feat: rewritten the most part of it

This commit is contained in:
Reinaldy Rafli 2022-09-03 17:53:46 +07:00
parent 215a160205
commit 3d7e1dd077
No known key found for this signature in database
GPG Key ID: EA1B2522C1693EC8
57 changed files with 723 additions and 3163 deletions

View File

@ -1,12 +1,21 @@
FROM golang:1.17-buster FROM golang:1.19.0-bullseye AS builder
WORKDIR /app WORKDIR /app
COPY . . COPY . .
RUN go mod download RUN go build -o main .
RUN go build -v main.go
FROM debian:bullseye AS runtime
WORKDIR /app
COPY --from=builder /app/main .
ENV PORT=5000
ENV HOSTNAME=0.0.0.0
ENV ENVIRONMENT=production
EXPOSE ${PORT} EXPOSE ${PORT}
CMD ["./main"] ENTRYPOINT [ "/app/main" ]

View File

@ -1,60 +0,0 @@
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)
c, err := db.Acquire(ctx)
if err != nil {
return 0, err
}
defer c.Release()
tx, err := c.Begin(ctx)
if err != nil {
return 0, err
}
defer tx.Rollback(ctx)
sql, args, err := query.
Update("administrators").
Set("last_used", time.Now().UTC().Format(time.RFC3339)).
ToSql()
if err != nil {
return 0, err
}
_, err = tx.Exec(ctx, sql, args...)
if err != nil {
return 0, err
}
sql, args, err = query.
Select("id").
From("administrators").
Where(squirrel.Eq{"key": key}).
ToSql()
if err != nil {
return 0, err
}
var id int
err = tx.QueryRow(ctx, sql, args...).Scan(&id)
if err != nil {
return 0, err
}
err = tx.Commit(ctx)
if err != nil {
return 0, err
}
return id, nil
}

View File

@ -1,57 +0,0 @@
package administrator_test
import (
"context"
"jokes-bapak2-api/core/administrator"
"testing"
"time"
)
func TestGetUserID_Success(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer Flush()
c, err := db.Acquire(ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
defer c.Release()
_, err = c.Exec(
ctx,
`INSERT INTO administrators (id, key, token, last_used) VALUES ($1, $2, $3, $4)`,
administratorsData...,
)
if err != nil {
t.Error("an error was thrown:", err)
}
id, err := administrator.GetUserID(db, ctx, "very secure")
if err != nil {
t.Error("an error was thrown:", err)
}
if id != 1 {
t.Error("id is not correct, want: 1, got:", id)
}
}
func TestGetUserID_Failed(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer Flush()
c, err := db.Acquire(ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
defer c.Release()
id, err := administrator.GetUserID(db, ctx, "very secure")
if err == nil {
t.Error("an error was expected, got:", id)
}
}

View File

@ -1,124 +0,0 @@
package administrator_test
import (
"context"
"os"
"testing"
"time"
"github.com/jackc/pgx/v4/pgxpool"
)
var db *pgxpool.Pool
var administratorsData = []interface{}{
1, "very secure", "not the real one", time.Now().Format(time.RFC3339),
}
func TestMain(m *testing.M) {
defer Teardown()
Setup()
os.Exit(m.Run())
}
func Setup() {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1*time.Minute))
defer cancel()
poolConfig, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
if err != nil {
panic(err)
}
db, err = pgxpool.ConnectConfig(ctx, poolConfig)
if err != nil {
panic(err)
}
conn, err := db.Acquire(ctx)
if err != nil {
panic(err)
}
defer conn.Release()
tx, err := conn.Begin(ctx)
if err != nil {
panic(err)
}
defer tx.Rollback(ctx)
_, err = tx.Exec(
ctx,
`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 {
panic(err)
}
err = tx.Commit(ctx)
if err != nil {
panic(err)
}
}
func Teardown() (err error) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1*time.Minute))
defer cancel()
defer db.Close()
c, err := db.Acquire(ctx)
if err != nil {
return err
}
defer c.Release()
tx, err := c.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
_, err = tx.Exec(ctx, "TRUNCATE TABLE submission RESTART IDENTITY CASCADE")
if err != nil {
return err
}
_, err = tx.Exec(ctx, "TRUNCATE TABLE jokesbapak2 RESTART IDENTITY CASCADE")
if err != nil {
return err
}
_, err = tx.Exec(ctx, "TRUNCATE TABLE administrators RESTART IDENTITY CASCADE")
if err != nil {
return err
}
err = tx.Commit(ctx)
if err != nil {
return err
}
return
}
func Flush() error {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
conn, err := db.Acquire(ctx)
if err != nil {
return err
}
defer conn.Release()
_, err = conn.Exec(ctx, "TRUNCATE TABLE administrators RESTART IDENTITY CASCADE")
if err != nil {
return err
}
return nil
}

View File

@ -1,41 +0,0 @@
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(ctx, sql, args...).Scan(&token)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return "", nil
}
return "", err
}
return token, nil
}

View File

@ -1,70 +0,0 @@
package administrator_test
import (
"context"
"jokes-bapak2-api/core/administrator"
"testing"
"time"
)
func TestCheckKeyExists_Success(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer Flush()
c, err := db.Acquire(ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
defer c.Release()
_, err = c.Exec(
ctx,
"INSERT INTO administrators (id, key, token, last_used) VALUES ($1, $2, $3, $4)",
administratorsData...,
)
if err != nil {
t.Error("an error was thrown:", err)
}
key, err := administrator.CheckKeyExists(db, ctx, "very secure")
if err != nil {
t.Error("an error was thrown:", err)
}
if key != "not the real one" {
t.Error("key isn't not the real one, got:", key)
}
}
func TestCheckKeyExists_Failing(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer Flush()
c, err := db.Acquire(ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
defer c.Release()
_, err = c.Exec(
ctx,
"INSERT INTO administrators (id, key, token, last_used) VALUES ($1, $2, $3, $4)",
administratorsData...,
)
if err != nil {
t.Error("an error was thrown:", err)
}
key, err := administrator.CheckKeyExists(db, ctx, "others")
if err != nil {
t.Error("an error was thrown:", err)
}
if key != "" {
t.Error("key is not empty, got:", key)
}
}

View File

@ -2,185 +2,96 @@ package joke
import ( import (
"context" "context"
"encoding/hex"
"errors" "errors"
"jokes-bapak2-api/core/schema" "fmt"
"io"
"log"
"math/rand" "math/rand"
"strconv" "strconv"
"time"
"github.com/Masterminds/squirrel"
"github.com/allegro/bigcache/v3" "github.com/allegro/bigcache/v3"
"github.com/georgysavva/scany/pgxscan" "github.com/go-redis/redis/v8"
"github.com/jackc/pgx" "github.com/minio/minio-go/v7"
"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. func GetRandomJoke(ctx context.Context, bucket *minio.Client, cache *redis.Client, memory *bigcache.BigCache) (image []byte, contentType string, err error) {
// Keep in mind, you will need to store it to memory yourself. totalJokes, err := GetTotalJoke(ctx, bucket, cache, memory)
func GetAllJSONJokes(db *pgxpool.Pool, ctx context.Context) ([]byte, error) {
conn, err := db.Acquire(ctx)
if err != nil { if err != nil {
return []byte{}, err return []byte{}, "", fmt.Errorf("getting total joke: %w", err)
} }
defer conn.Release()
var jokes []schema.Joke randomIndex := rand.Intn(totalJokes - 1)
results, err := conn.Query(ctx, "SELECT \"id\",\"link\" FROM \"jokesbapak2\" ORDER BY \"id\"")
joke, contentType, err := GetJokeById(ctx, bucket, cache, memory, randomIndex)
if err != nil { if err != nil {
return nil, err return []byte{}, "", fmt.Errorf("getting joke by id: %w", err)
} }
defer results.Close()
err = pgxscan.ScanAll(&jokes, results) return joke, contentType, nil
}
func GetJokeById(ctx context.Context, bucket *minio.Client, cache *redis.Client, memory *bigcache.BigCache, id int) (image []byte, contentType string, err error) {
jokeFromMemory, err := memory.Get("id:" + strconv.Itoa(id))
if err != nil && !errors.Is(err, bigcache.ErrEntryNotFound) {
return []byte{}, "", fmt.Errorf("acquiring joke from memory: %w", err)
}
if err == nil {
return jokeFromMemory, "", nil
}
jokeFromCache, err := cache.Get(ctx, "jokes:id:"+strconv.Itoa(id)).Result()
if err != nil { if err != nil {
return nil, err // Decode hex string to bytes
} imageBytes, err := hex.DecodeString(jokeFromCache)
data, err := ffjson.Marshal(jokes)
if err != nil { if err != nil {
return nil, err return []byte{}, "", fmt.Errorf("decoding hex string: %w", err)
} }
return data, nil defer func(id int, imageBytes []byte) {
} err := memory.Set("id:"+strconv.Itoa(id), imageBytes)
// Only return a link
func GetRandomJokeFromDB(db *pgxpool.Pool, ctx context.Context) (string, error) {
conn, err := db.Acquire(ctx)
if err != nil { if err != nil {
return "", err log.Printf("setting memory cache: %s", err.Error())
} }
defer conn.Release() }(id, imageBytes)
var link string return imageBytes, "", nil
err = conn.QueryRow(ctx, "SELECT link FROM jokesbapak2 ORDER BY random() LIMIT 1").Scan(&link) }
jokes, err := ListJokesFromBucket(ctx, bucket, cache)
if err != nil { if err != nil {
return "", err return []byte{}, "", fmt.Errorf("listing jokes: %w", err)
} }
return link, nil object, err := bucket.GetObject(ctx, JokesBapak2Bucket, jokes[id].FileName, minio.GetObjectOptions{})
}
// 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 err != nil {
if errors.Is(err, bigcache.ErrEntryNotFound) { return []byte{}, "", fmt.Errorf("getting object: %w", err)
return "", schema.ErrNotFound
} }
return "", err defer func() {
} err := object.Close()
var data []schema.Joke
err = ffjson.Unmarshal(jokes, &data)
if err != nil { if err != nil {
return "", nil log.Printf("closing image reader: %s", err.Error())
} }
}()
// Return an error if the database is empty image, err = io.ReadAll(object)
dataLength := len(data)
if dataLength == 0 {
return "", schema.ErrEmpty
}
random := rand.Intn(dataLength)
joke := data[random].Link
return joke, nil
}
// CheckJokesCache checks if there is some value inside jokes cache.
func CheckJokesCache(memory *bigcache.BigCache) (bool, error) {
_, err := memory.Get("jokes")
if err != nil { if err != nil {
if errors.Is(err, bigcache.ErrEntryNotFound) { return []byte{}, "", fmt.Errorf("reading object: %w", err)
return false, nil
}
return false, err
} }
return true, nil defer func(id int, image []byte) {
} ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
// CheckTotalJokesCache literally does what the name is for imageString := hex.EncodeToString(image)
func CheckTotalJokesCache(memory *bigcache.BigCache) (bool, error) {
_, err := memory.Get("total") err := cache.Set(ctx, "jokes:id:"+strconv.Itoa(id), imageString, time.Hour*1).Err()
if err != nil { if err != nil {
if errors.Is(err, bigcache.ErrEntryNotFound) { log.Printf("setting cache: %s", err.Error())
return false, nil
}
return false, err
} }
}(id, image)
return true, nil return image, jokes[id].ContentType, nil
}
// GetCachedJokeByID returns a link string of a certain ID from cache.
func GetCachedJokeByID(memory *bigcache.BigCache, id int) (string, error) {
jokes, err := memory.Get("jokes")
if err != nil {
if errors.Is(err, bigcache.ErrEntryNotFound) {
return "", schema.ErrNotFound
}
return "", err
}
var data []schema.Joke
err = ffjson.Unmarshal(jokes, &data)
if err != nil {
return "", err
}
// This is a simple solution, might convert it to goroutines and channels sometime soon.
for _, v := range data {
if v.ID == id {
return v.Link, nil
}
}
return "", nil
}
// GetCachedTotalJokes
func GetCachedTotalJokes(memory *bigcache.BigCache) (int, error) {
total, err := memory.Get("total")
if err != nil {
if errors.Is(err, bigcache.ErrEntryNotFound) {
return 0, schema.ErrNotFound
}
return 0, err
}
i, err := strconv.Atoi(string(total))
if err != nil {
return 0, err
}
return i, 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(ctx, sql, args...).Scan(&jokeID)
if err != nil && errors.Is(err, pgx.ErrNoRows) {
return false, err
}
return strconv.Itoa(jokeID) == id, nil
} }

View File

@ -1,341 +0,0 @@
package joke_test
import (
"context"
"encoding/json"
"jokes-bapak2-api/core/joke"
"jokes-bapak2-api/core/schema"
"testing"
"time"
"github.com/jackc/pgx/v4"
)
func TestGetAllJSONJokes(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer Flush()
conn, err := db.Acquire(ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
defer conn.Release()
err = conn.BeginFunc(ctx, func(t pgx.Tx) error {
_, err := t.Exec(
ctx,
`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(
ctx,
`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, ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
if string(j) == "" {
t.Error("j should not be empty")
}
}
func TestGetRandomJokeFromDB(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer Flush()
conn, err := db.Acquire(ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
defer conn.Release()
err = conn.BeginFunc(ctx, func(t pgx.Tx) error {
_, err := t.Exec(
ctx,
`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(
ctx,
`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.GetRandomJokeFromDB(db, ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
if j == "" {
t.Error("j should not be empty")
}
}
func TestGetRandomJokeFromCache(t *testing.T) {
defer Flush()
jokes := []schema.Joke{
{ID: 1, Link: "link1", Creator: 1},
{ID: 2, Link: "link2", Creator: 1},
{ID: 3, Link: "link3", Creator: 1},
}
data, err := json.Marshal(jokes)
if err != nil {
t.Error("an error was thrown:", err)
}
err = memory.Set("jokes", data)
if err != nil {
t.Error("an error was thrown:", err)
}
j, err := joke.GetRandomJokeFromCache(memory)
if err != nil {
t.Error("an error was thrown:", err)
}
if j == "" {
t.Error("j should not be empty")
}
}
func TestCheckJokesCache_True(t *testing.T) {
defer Flush()
jokes := []schema.Joke{
{ID: 1, Link: "link1", Creator: 1},
{ID: 2, Link: "link2", Creator: 1},
{ID: 3, Link: "link3", Creator: 1},
}
data, err := json.Marshal(jokes)
if err != nil {
t.Error("an error was thrown:", err)
}
err = memory.Set("jokes", data)
if err != nil {
t.Error("an error was thrown:", err)
}
j, err := joke.CheckJokesCache(memory)
if err != nil {
t.Error("an error was thrown:", err)
}
if j == false {
t.Error("j should not be false")
}
}
func TestCheckJokesCache_False(t *testing.T) {
defer Flush()
j, err := joke.CheckJokesCache(memory)
if err != nil {
t.Error("an error was thrown:", err)
}
if j == true {
t.Error("j should not be true")
}
}
func TestCheckTotalJokesCache_True(t *testing.T) {
defer Flush()
err := memory.Set("total", []byte("10"))
if err != nil {
t.Error("an error was thrown:", err)
}
j, err := joke.CheckTotalJokesCache(memory)
if err != nil {
t.Error("an error was thrown:", err)
}
if j == false {
t.Error("j should not be false")
}
}
func TestCheckTotalJokesCache_False(t *testing.T) {
defer Flush()
j, err := joke.CheckTotalJokesCache(memory)
if err != nil {
t.Error("an error was thrown:", err)
}
if j == true {
t.Error("j should not be true")
}
}
func TestGetCachedJokeByID(t *testing.T) {
defer Flush()
jokes := []schema.Joke{
{ID: 1, Link: "link1", Creator: 1},
{ID: 2, Link: "link2", Creator: 1},
{ID: 3, Link: "link3", Creator: 1},
}
data, err := json.Marshal(jokes)
if err != nil {
t.Error("an error was thrown:", err)
}
err = memory.Set("jokes", data)
if err != nil {
t.Error("an error was thrown:", err)
}
j, err := joke.GetCachedJokeByID(memory, 1)
if err != nil {
t.Error("an error was thrown:", err)
}
if j != "link1" {
t.Error("j should be link1, got:", j)
}
k, err := joke.GetCachedJokeByID(memory, 4)
if err != nil {
t.Error("an error was thrown:", err)
}
if k != "" {
t.Error("k should be empty, got:", k)
}
}
func TestGetCachedTotalJokes(t *testing.T) {
defer Flush()
err := memory.Set("total", []byte("10"))
if err != nil {
t.Error("an error was thrown:", err)
}
j, err := joke.GetCachedTotalJokes(memory)
if err != nil {
t.Error("an error was thrown:", err)
}
if j != 10 {
t.Error("j should be 10, got:", j)
}
}
func TestCheckJokeExists(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer Flush()
conn, err := db.Acquire(ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
defer conn.Release()
err = conn.BeginFunc(ctx, func(t pgx.Tx) error {
_, err := t.Exec(
ctx,
`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(
ctx,
`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.CheckJokeExists(db, ctx, "1")
if err != nil {
t.Error("an error was thrown:", err)
}
if j == false {
t.Error("j should not be false")
}
k, err := joke.CheckJokeExists(db, ctx, "4")
if err != nil {
t.Error("an error was thrown:", err)
}
if k == true {
t.Error("k should not be true")
}
}

View File

@ -1,195 +0,0 @@
package joke_test
import (
"context"
"os"
"testing"
"time"
"github.com/allegro/bigcache/v3"
"github.com/go-redis/redis/v8"
"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 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) {
defer Teardown()
Setup()
time.Sleep(3 * time.Second)
os.Exit(m.Run())
}
func Setup() {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1*time.Minute))
defer cancel()
poolConfig, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
if err != nil {
panic(err)
}
db, err = pgxpool.ConnectConfig(ctx, 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(ctx)
if err != nil {
panic(err)
}
defer conn.Release()
tx, err := conn.Begin(ctx)
if err != nil {
panic(err)
}
defer tx.Rollback(ctx)
_, err = tx.Exec(
ctx,
`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 {
panic(err)
}
_, err = tx.Exec(
ctx,
`CREATE TABLE IF NOT EXISTS jokesbapak2 (
id SERIAL PRIMARY KEY,
link TEXT UNIQUE,
creator INT NOT NULL REFERENCES "administrators" ("id")
)`,
)
if err != nil {
panic(err)
}
_, err = tx.Exec(
ctx,
`CREATE TABLE IF NOT EXISTS submission (
id SERIAL PRIMARY KEY,
link VARCHAR(255) UNIQUE NOT NULL,
created_at VARCHAR(255),
author VARCHAR(255) NOT NULL,
status SMALLINT DEFAULT 0
)`,
)
if err != nil {
panic(err)
}
err = tx.Commit(ctx)
if err != nil {
panic(err)
}
}
func Teardown() (err error) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer db.Close()
conn, err := db.Acquire(ctx)
if err != nil {
return err
}
defer conn.Release()
tx, err := conn.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
_, err = tx.Exec(ctx, "TRUNCATE TABLE submission RESTART IDENTITY CASCADE")
if err != nil {
return err
}
_, err = tx.Exec(ctx, "TRUNCATE TABLE jokesbapak2 RESTART IDENTITY CASCADE")
if err != nil {
return err
}
_, err = tx.Exec(ctx, "TRUNCATE TABLE administrators RESTART IDENTITY CASCADE")
if err != nil {
return err
}
err = tx.Commit(ctx)
if err != nil {
return err
}
err = cache.Close()
if err != nil {
return
}
err = memory.Close()
return
}
func Flush() error {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
conn, err := db.Acquire(ctx)
if err != nil {
return err
}
defer conn.Release()
_, err = conn.Exec(ctx, "TRUNCATE TABLE submission RESTART IDENTITY CASCADE")
if err != nil {
return err
}
_, err = conn.Exec(ctx, "TRUNCATE TABLE jokesbapak2 RESTART IDENTITY CASCADE")
if err != nil {
return err
}
_, err = conn.Exec(ctx, "TRUNCATE TABLE administrators RESTART IDENTITY CASCADE")
if err != nil {
return err
}
err = cache.FlushAll(ctx).Err()
if err != nil {
return err
}
err = memory.Reset()
if err != nil {
return err
}
return nil
}

11
api/core/joke/joke.go Normal file
View File

@ -0,0 +1,11 @@
package joke
import "time"
const JokesBapak2Bucket = "jokesbapak2"
type Joke struct {
FileName string
ContentType string
ModifiedAt time.Time
}

View File

@ -0,0 +1,91 @@
package joke_test
import (
"context"
"log"
"os"
"testing"
"time"
"github.com/allegro/bigcache/v3"
"github.com/go-redis/redis/v8"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
var bucket *minio.Client
var cache *redis.Client
var memory *bigcache.BigCache
func TestMain(m *testing.M) {
redisUrl, ok := os.LookupEnv("REDIS_URL")
if !ok {
redisUrl = "redis://@localhost:6379"
}
minioHost, ok := os.LookupEnv("MINIO_HOST")
if !ok {
minioHost = "localhost:9000"
}
minioID, ok := os.LookupEnv("MINIO_ACCESS_ID")
if !ok {
minioID = "minio"
}
minioSecret, ok := os.LookupEnv("MINIO_SECRET_KEY")
if !ok {
minioSecret = "password"
}
minioToken, ok := os.LookupEnv("MINIO_TOKEN")
if !ok {
minioToken = ""
}
parsedRedisUrl, err := redis.ParseURL(redisUrl)
if err != nil {
log.Fatalf("parsing redis url: %s", err.Error())
return
}
redisClient := redis.NewClient(parsedRedisUrl)
minioClient, err := minio.New(minioHost, &minio.Options{
Creds: credentials.NewStaticV4(minioID, minioSecret, minioToken),
})
if err != nil {
log.Fatalf("creating minio client: %s", err.Error())
}
memoryInstance, err := bigcache.NewBigCache(bigcache.DefaultConfig(time.Second * 30))
if err != nil {
log.Fatalf("creating bigcache client: %s", err.Error())
}
bucket = minioClient
cache = redisClient
memory = memoryInstance
exitCode := m.Run()
cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), time.Minute)
defer cleanupCancel()
err = redisClient.FlushAll(cleanupCtx).Err()
if err != nil {
log.Printf("flushing redis: %s", err.Error())
}
err = cache.Close()
if err != nil {
log.Printf("closing cache client: %s", err.Error())
}
err = redisClient.Close()
if err != nil {
log.Printf("closing redis client: %s", err.Error())
}
os.Exit(exitCode)
}

66
api/core/joke/list.go Normal file
View File

@ -0,0 +1,66 @@
package joke
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"sort"
"time"
"github.com/go-redis/redis/v8"
"github.com/minio/minio-go/v7"
)
func ListJokesFromBucket(ctx context.Context, bucket *minio.Client, cache *redis.Client) ([]Joke, error) {
cached, err := cache.Get(ctx, "jokes:list").Result()
if err != nil && !errors.Is(err, redis.Nil) {
return []Joke{}, fmt.Errorf("acquiring joke list from cache: %w", err)
}
if err == nil {
var jokes []Joke
err := json.Unmarshal([]byte(cached), &jokes)
if err != nil {
return []Joke{}, fmt.Errorf("unmarshalling json: %w", err)
}
return jokes, nil
}
objects := bucket.ListObjects(ctx, JokesBapak2Bucket, minio.ListObjectsOptions{Recursive: true})
var jokes []Joke
for object := range objects {
if object.Err != nil {
return []Joke{}, fmt.Errorf("enumerating objects: %w", object.Err)
}
if !object.IsDeleteMarker {
jokes = append(jokes, Joke{ModifiedAt: object.Restore.ExpiryTime, FileName: object.Key, ContentType: object.ContentType})
}
}
sort.SliceStable(jokes, func(i, j int) bool {
return jokes[i].ModifiedAt.Before(jokes[i].ModifiedAt)
})
defer func(jokes []Joke) {
ctx, cancel := context.WithTimeout(context.Background(), time.Minute)
defer cancel()
marshalled, err := json.Marshal(jokes)
if err != nil {
log.Printf("marshalling json: %s", err.Error())
return
}
err = cache.Set(ctx, "jokes:list", string(marshalled), time.Hour*6).Err()
if err != nil {
log.Printf("setting jokes:list cache: %s", err.Error())
}
}(jokes)
return jokes, nil
}

View File

@ -1,135 +0,0 @@
package joke
import (
"context"
"strconv"
"jokes-bapak2-api/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(strconv.Itoa(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(ctx, 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(ctx, sql, args...)
if err != nil {
return err
}
defer r.Close()
return nil
}
func UpdateJoke(db *pgxpool.Pool, ctx context.Context, newJoke 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.
Update("jokesbapak2").
Set("link", newJoke.Link).
Set("creator", newJoke.Creator).
Where(squirrel.Eq{"id": newJoke.ID}).
ToSql()
if err != nil {
return err
}
r, err := conn.Query(ctx, sql, args...)
if err != nil {
return err
}
defer r.Close()
return nil
}

View File

@ -1,238 +0,0 @@
package joke_test
import (
"context"
"jokes-bapak2-api/core/joke"
"jokes-bapak2-api/core/schema"
"testing"
"time"
"github.com/jackc/pgx/v4"
)
func TestSetAllJSONJoke(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer Flush()
conn, err := db.Acquire(ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
defer conn.Release()
err = conn.BeginFunc(ctx, func(t pgx.Tx) error {
_, err := t.Exec(
ctx,
`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(
ctx,
`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)
}
err = joke.SetAllJSONJoke(db, ctx, memory)
if err != nil {
t.Error("an error was thrown:", err)
}
}
func TestSetTotalJoke(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer Flush()
conn, err := db.Acquire(ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
defer conn.Release()
err = conn.BeginFunc(ctx, func(t pgx.Tx) error {
_, err := t.Exec(
ctx,
`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(
ctx,
`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)
}
err = joke.SetTotalJoke(db, ctx, memory)
if err != nil {
t.Error("an error was thrown:", err)
}
}
func TestInsertJokeIntoDB(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer Flush()
data := schema.Joke{
ID: 1,
Link: "link1",
Creator: 1,
}
err := joke.InsertJokeIntoDB(db, ctx, data)
if err != nil {
t.Error("an error was thrown:", err)
}
}
func TestDeleteSingleJoke(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer Flush()
conn, err := db.Acquire(ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
defer conn.Release()
err = conn.BeginFunc(ctx, func(t pgx.Tx) error {
_, err := t.Exec(
ctx,
`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(
ctx,
`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)
}
err = joke.DeleteSingleJoke(db, ctx, 1)
if err != nil {
t.Error("an error was thrown:", err)
}
}
func TestUpdateJoke(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer Flush()
conn, err := db.Acquire(ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
defer conn.Release()
err = conn.BeginFunc(ctx, func(t pgx.Tx) error {
_, err := t.Exec(
ctx,
`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(
ctx,
`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)
}
newJoke := schema.Joke{
ID: 1,
Link: "link1",
Creator: 1,
}
err = joke.UpdateJoke(db, ctx, newJoke)
if err != nil {
t.Error("an error was thrown:", err)
}
}

76
api/core/joke/today.go Normal file
View File

@ -0,0 +1,76 @@
package joke
import (
"context"
"encoding/hex"
"errors"
"fmt"
"log"
"time"
"github.com/allegro/bigcache/v3"
"github.com/go-redis/redis/v8"
"github.com/minio/minio-go/v7"
)
func GetTodaysJoke(ctx context.Context, bucket *minio.Client, cache *redis.Client, memory *bigcache.BigCache) (image []byte, contentType string, err error) {
// Today's date:
today := time.Now().Format("2006-01-02")
jokeFromMemory, err := memory.Get("today:file:" + today)
if err != nil && !errors.Is(err, bigcache.ErrEntryNotFound) {
return []byte{}, "", fmt.Errorf("acquiring joke from memory: %w", err)
}
if err == nil {
contentTypeFromMemory, err := memory.Get("today:content-type:" + today)
if err != nil && !errors.Is(err, bigcache.ErrEntryNotFound) {
return []byte{}, "", fmt.Errorf("acquiring joke content type from memory: %w", err)
}
return jokeFromMemory, string(contentTypeFromMemory), nil
}
jokeFromCache, err := cache.Get(ctx, "jokes:today:"+today).Result()
if err != nil && !errors.Is(err, redis.Nil) {
return []byte{}, "", fmt.Errorf("acquiring joke from cache: %w", err)
}
if err == nil {
// Decode hex string to bytes
imageBytes, err := hex.DecodeString(jokeFromCache)
if err != nil {
return []byte{}, "", fmt.Errorf("decoding hex string: %w", err)
}
defer func(today string, imageBytes []byte) {
err := memory.Set("today:"+today, imageBytes)
if err != nil {
log.Printf("setting memory cache: %s", err.Error())
}
}(today, imageBytes)
return imageBytes, "", nil
}
// If everything not exists, we get a new random joke
randomJoke, contentType, err := GetRandomJoke(ctx, bucket, cache, memory)
if err != nil {
return []byte{}, "", fmt.Errorf("acquiring new random joke: %w", err)
}
defer func(today string, joke []byte) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
// Encode to hex string
encodedImage := hex.EncodeToString(joke)
err := cache.Set(ctx, "jokes:today:"+today, encodedImage, time.Hour*24).Err()
if err != nil {
log.Printf("setting today cache to redis: %s", err.Error())
}
}(today, randomJoke)
return randomJoke, contentType, nil
}

66
api/core/joke/total.go Normal file
View File

@ -0,0 +1,66 @@
package joke
import (
"context"
"errors"
"fmt"
"log"
"strconv"
"time"
"github.com/allegro/bigcache/v3"
"github.com/go-redis/redis/v8"
"github.com/minio/minio-go/v7"
)
func GetTotalJoke(ctx context.Context, bucket *minio.Client, cache *redis.Client, memory *bigcache.BigCache) (int, error) {
totalJokesFromMemory, err := memory.Get("total")
if err != nil && !errors.Is(err, bigcache.ErrEntryNotFound) {
return 0, fmt.Errorf("acquiring total joke from memory: %w", err)
}
if err == nil {
total, err := strconv.Atoi(string(totalJokesFromMemory))
if err != nil {
return 0, fmt.Errorf("parsing string to int: %w", err)
}
return total, nil
}
totalJokesFromCache, err := cache.Get(ctx, "jokes:total").Result()
if err != nil && !errors.Is(err, redis.Nil) {
return 0, fmt.Errorf("acquiring total joke from redis: %w", err)
}
if err == nil {
total, err := strconv.Atoi(string(totalJokesFromCache))
if err != nil {
return 0, fmt.Errorf("parsing string to int: %w", err)
}
return total, nil
}
jokes, err := ListJokesFromBucket(ctx, bucket, cache)
if err != nil {
return 0, fmt.Errorf("listing jokes: %w", err)
}
defer func(total int) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
defer cancel()
err := cache.Set(ctx, "jokes:total", strconv.Itoa(total), time.Hour*3).Err()
if err != nil {
log.Printf("setting total jokes to redis: %s", err.Error())
}
err = memory.Set("total", []byte(strconv.Itoa(total)))
if err != nil {
log.Printf("setting total jokes to memory: %s", err.Error())
}
}(len(jokes))
return len(jokes), nil
}

27
api/core/joke/uploader.go Normal file
View File

@ -0,0 +1,27 @@
package joke
import (
"context"
"fmt"
"io"
"github.com/minio/minio-go/v7"
)
func Uploader(bucket *minio.Client, ctx context.Context, key string, payload io.Reader, fileSize int64, contentType string) (string, error) {
info, err := bucket.PutObject(
ctx,
JokesBapak2Bucket, // bucketName
key, // object name,
payload, // reader
fileSize, // obuject size,
minio.PutObjectOptions{
ContentType: contentType,
},
)
if err != nil {
return "", fmt.Errorf("uploading object: %w", err)
}
return info.Key, nil
}

View File

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

View File

@ -1,12 +1,12 @@
package schema package schema
type Submission struct { type Submission struct {
ID int `json:"id,omitempty" db:"id"` ID int `json:"id,omitempty"`
Link string `json:"link" form:"link" db:"link"` Link string `json:"link"`
Image string `json:"image,omitempty" form:"image"` Image string `json:"image,omitempty"`
CreatedAt string `json:"created_at" db:"created_at"` CreatedAt string `json:"created_at"`
Author string `json:"author" form:"author" db:"author"` Author string `json:"author"`
Status int `json:"status" db:"status"` Status int `json:"status"`
} }
type SubmissionQuery struct { type SubmissionQuery struct {

View File

@ -1,115 +0,0 @@
package submit
import (
"context"
"jokes-bapak2-api/core/schema"
"net/url"
"strconv"
"strings"
"github.com/aldy505/bob"
"github.com/georgysavva/scany/pgxscan"
"github.com/jackc/pgx/v4/pgxpool"
)
func GetSubmittedItems(db *pgxpool.Pool, ctx context.Context, queries schema.SubmissionQuery) ([]schema.Submission, error) {
var err error
var limit int
var offset int
var approved bool
if queries.Limit != "" {
limit, err = strconv.Atoi(queries.Limit)
if err != nil {
return []schema.Submission{}, err
}
}
if queries.Page != "" {
page, err := strconv.Atoi(queries.Page)
if err != nil {
return []schema.Submission{}, err
}
offset = (page - 1) * 20
}
if queries.Approved != "" {
approved, err = strconv.ParseBool(queries.Approved)
if err != nil {
return []schema.Submission{}, err
}
}
var status int
if approved {
status = 1
} else {
status = 0
}
sql, args, err := GetterQueryBuilder(queries, status, limit, offset)
if err != nil {
return []schema.Submission{}, err
}
conn, err := db.Acquire(ctx)
if err != nil {
return []schema.Submission{}, err
}
defer conn.Release()
var submissions []schema.Submission
results, err := conn.Query(ctx, sql, args...)
if err != nil {
return []schema.Submission{}, err
}
defer results.Close()
err = pgxscan.ScanAll(&submissions, results)
if err != nil {
return []schema.Submission{}, err
}
return submissions, nil
}
func GetterQueryBuilder(queries schema.SubmissionQuery, status, limit, offset int) (string, []interface{}, error) {
var sql string
var args []interface{}
var sqlQuery strings.Builder
sqlQuery.WriteString("SELECT * FROM submission WHERE TRUE")
if queries.Author != "" {
sqlQuery.WriteString(" AND author = ?")
escapedAuthor, err := url.QueryUnescape(queries.Author)
if err != nil {
return sql, args, err
}
args = append(args, escapedAuthor)
}
if queries.Approved != "" {
sqlQuery.WriteString(" AND status = ?")
args = append(args, status)
}
if limit > 0 {
sqlQuery.WriteString(" LIMIT " + strconv.Itoa(limit))
} else {
sqlQuery.WriteString(" LIMIT 20")
}
if queries.Page != "" {
sqlQuery.WriteString(" OFFSET " + strconv.Itoa(offset))
}
sql = bob.ReplacePlaceholder(sqlQuery.String(), bob.Dollar)
return sql, args, nil
}

View File

@ -1,91 +0,0 @@
package submit_test
import (
"context"
"jokes-bapak2-api/core/schema"
"jokes-bapak2-api/core/submit"
"testing"
"time"
)
func TestGetSubmittedItems(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer Flush()
c, err := db.Acquire(ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
defer c.Release()
tx, err := c.Begin(ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
defer tx.Rollback(ctx)
_, err = tx.Exec(
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 {
t.Error("an error was thrown:", err)
}
err = tx.Commit(ctx)
if err != nil {
t.Error("an error was thrown:", err)
}
items, err := submit.GetSubmittedItems(db, ctx, schema.SubmissionQuery{})
if err != nil {
t.Error("an error was thrown:", err)
}
if len(items) != 2 {
t.Error("expected 2 items, got", len(items))
}
}
func TestGetterQueryBuilder(t *testing.T) {
s, _, err := submit.GetterQueryBuilder(schema.SubmissionQuery{}, 0, 0, 0)
if err != nil {
t.Error("an error was thrown:", err)
}
if s != "SELECT * FROM submission WHERE TRUE LIMIT 20" {
t.Error("expected query to be", "SELECT * FROM submission WHERE TRUE LIMIT 20", "got", s)
}
s, i, err := submit.GetterQueryBuilder(schema.SubmissionQuery{
Author: "Test <example@test.com>",
Approved: "true",
Page: "2",
}, 2, 15, 10)
if err != nil {
t.Error("an error was thrown:", err)
}
if s != "SELECT * FROM submission WHERE TRUE AND author = $1 AND status = $2 LIMIT 15 OFFSET 10" {
t.Error(
"expected query to be",
"SELECT * FROM submission WHERE TRUE AND author = $1 AND status = $2 LIMIT 15 OFFSET 15",
"got:",
s,
)
}
if i[0].(string) != "Test <example@test.com>" {
t.Error("expected first arg to be Test <example@test.com>, got:", i[0].(string))
}
if i[1].(int) != 2 {
t.Error("expected second arg to be 1, got:", i[1].(int))
}
}

View File

@ -1,128 +0,0 @@
package submit_test
import (
"context"
"os"
"testing"
"time"
"github.com/jackc/pgx/v4/pgxpool"
)
var db *pgxpool.Pool
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,
}
func TestMain(m *testing.M) {
defer Teardown()
Setup()
time.Sleep(3 * time.Second)
os.Exit(m.Run())
}
func Setup() {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1*time.Minute))
defer cancel()
poolConfig, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL"))
if err != nil {
panic(err)
}
db, err = pgxpool.ConnectConfig(ctx, poolConfig)
if err != nil {
panic(err)
}
conn, err := db.Acquire(ctx)
if err != nil {
panic(err)
}
defer conn.Release()
tx, err := conn.Begin(ctx)
if err != nil {
panic(err)
}
defer tx.Rollback(ctx)
_, err = tx.Exec(
ctx,
`CREATE TABLE IF NOT EXISTS submission (
id SERIAL PRIMARY KEY,
link VARCHAR(255) UNIQUE NOT NULL,
created_at VARCHAR(255),
author VARCHAR(255) NOT NULL,
status SMALLINT DEFAULT 0
)`,
)
if err != nil {
panic(err)
}
err = tx.Commit(ctx)
if err != nil {
panic(err)
}
}
func Teardown() (err error) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer db.Close()
conn, err := db.Acquire(ctx)
if err != nil {
return err
}
defer conn.Release()
tx, err := conn.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
_, err = tx.Exec(ctx, "DROP TABLE IF EXISTS submission CASCADE")
if err != nil {
return err
}
_, err = tx.Exec(ctx, "DROP TABLE IF EXISTS jokesbapak2 CASCADE")
if err != nil {
return err
}
_, err = tx.Exec(ctx, "DROP TABLE IF EXISTS administrators CASCADE")
if err != nil {
return err
}
err = tx.Commit(ctx)
if err != nil {
return err
}
return
}
func Flush() error {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
conn, err := db.Acquire(ctx)
if err != nil {
return err
}
defer conn.Release()
_, err = conn.Exec(ctx, "TRUNCATE TABLE submission RESTART IDENTITY CASCADE")
if err != nil {
return err
}
return nil
}

View File

@ -1,122 +0,0 @@
package submit
import (
"bytes"
"context"
"io"
"io/ioutil"
"jokes-bapak2-api/core/schema"
"jokes-bapak2-api/utils"
"mime/multipart"
"net/http"
"net/url"
"os"
"time"
"github.com/Masterminds/squirrel"
"github.com/georgysavva/scany/pgxscan"
"github.com/gojek/heimdall/v7/httpclient"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/pquerna/ffjson/ffjson"
)
// UploadImage process the image from the user to be uploaded to the cloud storage.
// Returns the image URL.
func UploadImage(client *httpclient.Client, image io.Reader) (string, error) {
hostURL := os.Getenv("IMAGE_API_URL")
fileName, err := utils.RandomString(10)
if err != nil {
return "", err
}
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
fw, err := writer.CreateFormField("image")
if err != nil {
return "", err
}
_, err = io.Copy(fw, image)
if err != nil {
return "", err
}
err = writer.Close()
if err != nil {
return "", err
}
headers := http.Header{
"Content-Type": []string{writer.FormDataContentType()},
"User-Agent": []string{"JokesBapak2 API"},
"Accept": []string{"application/json"},
}
requestURL, err := url.Parse(hostURL)
if err != nil {
return "", err
}
params := url.Values{}
params.Add("key", os.Getenv("IMAGE_API_KEY"))
params.Add("name", fileName)
requestURL.RawQuery = params.Encode()
res, err := client.Post(requestURL.String(), bytes.NewReader(body.Bytes()), headers)
if err != nil {
return "", err
}
defer res.Body.Close()
responseBody, err := ioutil.ReadAll(res.Body)
if err != nil {
return "", err
}
var data schema.ImageAPI
err = ffjson.Unmarshal(responseBody, &data)
if err != nil {
return "", err
}
return data.Data.URL, nil
}
func SubmitJoke(db *pgxpool.Pool, ctx context.Context, s schema.Submission, link string) (schema.Submission, error) {
var query = squirrel.StatementBuilder.PlaceholderFormat(squirrel.Dollar)
conn, err := db.Acquire(ctx)
if err != nil {
return schema.Submission{}, err
}
defer conn.Release()
now := time.Now().UTC().Format(time.RFC3339)
sql, args, err := query.
Insert("submission").
Columns("link", "created_at", "author").
Values(link, now, s.Author).
Suffix("RETURNING id,created_at,link,author,status").
ToSql()
if err != nil {
return schema.Submission{}, err
}
var submission schema.Submission
result, err := conn.Query(ctx, sql, args...)
if err != nil {
return schema.Submission{}, err
}
defer result.Close()
err = pgxscan.ScanOne(&submission, result)
if err != nil {
return schema.Submission{}, err
}
return submission, nil
}

View File

@ -1,25 +0,0 @@
package submit_test
import (
"context"
"jokes-bapak2-api/core/schema"
"jokes-bapak2-api/core/submit"
"testing"
"time"
)
func TestSubmitJoke(t *testing.T) {
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
defer cancel()
defer Flush()
s, err := submit.SubmitJoke(db, ctx, schema.Submission{Author: "Test <example@test.com>"}, "https://example.net/img.png")
if err != nil {
t.Error("an error was thrown:", err)
}
if s.Link != "https://example.net/img.png" {
t.Error("link is not correct, got:", s.Link)
}
}

View File

@ -1,25 +0,0 @@
package validator
import (
"regexp"
"strings"
)
func ValidateAuthor(author string) bool {
if len(author) > 200 {
return false
}
split := strings.Split(author, " ")
if strings.HasPrefix(split[0], "<") && strings.HasSuffix(split[0], ">") {
return false
}
if !strings.HasPrefix(split[len(split)-1], "<") && !strings.HasSuffix(split[len(split)-1], ">") {
return false
}
email := strings.Replace(split[len(split)-1], "<", "", 1)
email = strings.Replace(email, ">", "", 1)
pattern := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
return pattern.MatchString(email)
}

View File

@ -1,40 +0,0 @@
package validator_test
import (
"jokes-bapak2-api/core/validator"
"testing"
)
func TestValidateAuthor_False(t *testing.T) {
v := validator.ValidateAuthor("Test Author")
if v != false {
t.Error("Expected false, got true")
}
v = validator.ValidateAuthor("Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula eget dolor. Aenean massa. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Donec quam felis, ultricies nec, pellentesque eu, pretium quis, sem. Nulla consequat massa quis enim. Donec.")
if v != false {
t.Error("Expected false, got true")
}
v = validator.ValidateAuthor("")
if v != false {
t.Error("Expected false, got true")
}
v = validator.ValidateAuthor("Test <Author")
if v != false {
t.Error("Expected false, got true")
}
v = validator.ValidateAuthor("Test <Author>")
if v != false {
t.Error("Expected false, got true")
}
}
func TestValidateAuthor_True(t *testing.T) {
v := validator.ValidateAuthor("Test Author <author@mail.com>")
if v != true {
t.Error("Expected true, got false")
}
}

View File

@ -1,30 +0,0 @@
package validator
import (
"errors"
"jokes-bapak2-api/utils"
"net/http"
"strings"
"github.com/gojek/heimdall/v7/httpclient"
)
var ValidContentType = []string{"image/jpeg", "image/png", "image/webp", "image/gif"}
// CheckImageValidity calls to the image host to check whether it's a valid image or not.
func CheckImageValidity(client *httpclient.Client, link string) (bool, error) {
if strings.Contains(link, "https://") {
// Perform HTTP call to link
res, err := client.Get(link, http.Header{"User-Agent": []string{"JokesBapak2 API"}})
if err != nil {
return false, err
}
if res.StatusCode == 200 && utils.IsIn(ValidContentType, res.Header.Get("content-type")) {
return true, nil
}
return false, nil
}
return false, errors.New("URL must use HTTPS protocol")
}

View File

@ -1,50 +0,0 @@
package validator_test
import (
"jokes-bapak2-api/core/validator"
"testing"
"github.com/gojek/heimdall/v7/httpclient"
)
func TestCheckImageValidity_Error(t *testing.T) {
client := httpclient.NewClient()
b, err := validator.CheckImageValidity(client, "http://lorem-ipsum")
if err == nil {
t.Error("Expected error, got nil")
}
if b {
t.Error("Expected false, got true")
}
if err.Error() != "URL must use HTTPS protocol" {
t.Error("Expected error to be URL must use HTTPS protocol, got:", err)
}
}
func TestCheckImageValidity_False(t *testing.T) {
client := httpclient.NewClient()
b, err := validator.CheckImageValidity(client, "https://www.youtube.com/watch?v=yTJV6T37Reo")
if err != nil {
t.Error("Expected nil, got error")
}
if b {
t.Error("Expected false, got true")
}
}
func TestCheckImageValidity_True(t *testing.T) {
client := httpclient.NewClient()
b, err := validator.CheckImageValidity(client, "https://i.ytimg.com/vi/yTJV6T37Reo/maxresdefault.jpg")
if err != nil {
t.Error("Expected nil, got error")
}
if !b {
t.Error("Expected true, got false")
}
}

View File

@ -1,69 +0,0 @@
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 JokeLinkExists(db *pgxpool.Pool, ctx context.Context, link 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("link").
From("jokesbapak2").
Where(squirrel.Eq{"link": link}).
ToSql()
if err != nil {
return false, err
}
var validateLink string
err = conn.QueryRow(ctx, sql, args...).Scan(&validateLink)
if err != nil && err != pgx.ErrNoRows {
return false, err
}
return validateLink != "", nil
}
// Check if the joke exists
func JokeIDExists(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(ctx, 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,38 +0,0 @@
package validator
import (
"context"
"github.com/Masterminds/squirrel"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/pgxpool"
)
func SubmitLinkExists(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").
Where(squirrel.Eq{"link": link}).
ToSql()
if err != nil {
return false, err
}
var validateLink string
err = conn.QueryRow(ctx, sql, args...).Scan(&validateLink)
if err != nil && err != pgx.ErrNoRows {
return false, err
}
if err == nil && validateLink != "" {
return true, nil
}
return false, nil
}

View File

@ -1,37 +1,30 @@
module jokes-bapak2-api module jokes-bapak2-api
go 1.17 go 1.19
require ( require (
github.com/Masterminds/squirrel v1.5.1
github.com/aldy505/bob v0.0.4 github.com/aldy505/bob v0.0.4
github.com/aldy505/phc-crypto v1.1.0
github.com/allegro/bigcache/v3 v3.0.1 github.com/allegro/bigcache/v3 v3.0.1
github.com/georgysavva/scany v0.2.9
github.com/getsentry/sentry-go v0.11.0 github.com/getsentry/sentry-go v0.11.0
github.com/go-redis/redis/v8 v8.11.4 github.com/go-redis/redis/v8 v8.11.4
github.com/gofiber/fiber/v2 v2.21.0
github.com/gojek/heimdall/v7 v7.0.2
github.com/jackc/pgx v3.6.2+incompatible
github.com/jackc/pgx/v4 v4.13.0 github.com/jackc/pgx/v4 v4.13.0
github.com/joho/godotenv v1.4.0
github.com/kr/text v0.2.0 // indirect
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7
github.com/stretchr/testify v1.7.0 // indirect golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/sys v0.0.0-20211108224332-cbcd623f202e // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
) )
require ( require (
github.com/andybalholm/brotli v1.0.4 // indirect github.com/go-chi/chi/v5 v5.0.7
github.com/minio/minio-go/v7 v7.0.35
)
require (
github.com/cespare/xxhash/v2 v2.1.2 // indirect github.com/cespare/xxhash/v2 v2.1.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/gojek/valkyrie v0.0.0-20190210220504-8f62c1e7ba45 // indirect github.com/google/uuid v1.3.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // 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.10.0 // indirect github.com/jackc/pgconn v1.10.0 // indirect
github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
@ -39,15 +32,20 @@ require (
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect
github.com/jackc/pgtype v1.8.1 // indirect github.com/jackc/pgtype v1.8.1 // indirect
github.com/jackc/puddle v1.1.4 // indirect github.com/jackc/puddle v1.1.4 // indirect
github.com/klauspost/compress v1.13.6 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.15.9 // indirect
github.com/klauspost/cpuid/v2 v2.1.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rs/xid v1.4.0 // indirect
github.com/stretchr/objx v0.3.0 // indirect github.com/sirupsen/logrus v1.9.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
github.com/valyala/fasthttp v1.31.0 // indirect golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect
golang.org/x/text v0.3.7 // indirect golang.org/x/text v0.3.7 // indirect
gopkg.in/ini.v1 v1.66.6 // indirect
) )

View File

@ -2,32 +2,20 @@ github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOv
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
github.com/DataDog/datadog-go v3.7.1+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/Masterminds/squirrel v1.5.1 h1:kWAKlLLJFxZG7N2E0mBMNWVp5AuUX+JUrnhFN74Eg+w=
github.com/Masterminds/squirrel v1.5.1/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/aldy505/bob v0.0.4 h1:36lj6JUHxGp7yt672aKcC8gk6rXpIRO/aqclQ9aXDa8= github.com/aldy505/bob v0.0.4 h1:36lj6JUHxGp7yt672aKcC8gk6rXpIRO/aqclQ9aXDa8=
github.com/aldy505/bob v0.0.4/go.mod h1:uckrZqhg9zmbLA4MpKueIeQfrdriNqbmMalvf0+qPG4= github.com/aldy505/bob v0.0.4/go.mod h1:uckrZqhg9zmbLA4MpKueIeQfrdriNqbmMalvf0+qPG4=
github.com/aldy505/phc-crypto v1.1.0 h1:BagRKCrB7FOYy5vnuXR6xs6ml2gJD/CvSJkX/Ozo63w=
github.com/aldy505/phc-crypto v1.1.0/go.mod h1:LJugClOkOWKnpLrWhSaIDRN/5ftvZPD48S5oXsT7iTg=
github.com/allegro/bigcache/v3 v3.0.1 h1:Q4Xl3chywXuJNOw7NV+MeySd3zGQDj4KCpkCg0te8mc= github.com/allegro/bigcache/v3 v3.0.1 h1:Q4Xl3chywXuJNOw7NV+MeySd3zGQDj4KCpkCg0te8mc=
github.com/allegro/bigcache/v3 v3.0.1/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= github.com/allegro/bigcache/v3 v3.0.1/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I=
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY=
github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g=
github.com/cactus/go-statsd-client/statsd v0.0.0-20200423205355-cb0885a1018c/go.mod h1:l/bIBLeOl9eX+wxJAzxS4TveKRtAqlyDpHjhkfO0MEI=
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/cockroach-go/v2 v2.0.3 h1:ZA346ACHIZctef6trOTwBAEvPVm1k0uLm/bb2Atc+S8=
github.com/cockroachdb/cockroach-go/v2 v2.0.3/go.mod h1:hAuDgiVgDVkfirP9JnhXEfcXEPRKBpYdGz+l7mvYSzw=
github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
@ -36,19 +24,17 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
@ -57,13 +43,13 @@ github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/georgysavva/scany v0.2.9 h1:Xt6rjYpHnMClTm/g+oZTnoSxUwiln5GqMNU+QeLNHQU=
github.com/georgysavva/scany v0.2.9/go.mod h1:yeOeC1BdIdl6hOwy8uefL2WNSlseFzbhlG/frrh65SA=
github.com/getsentry/sentry-go v0.11.0 h1:qro8uttJGvNAMr5CLcFI9CHR0aDzXl0Vs3Pmw/oTPg8= github.com/getsentry/sentry-go v0.11.0 h1:qro8uttJGvNAMr5CLcFI9CHR0aDzXl0Vs3Pmw/oTPg8=
github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo= github.com/getsentry/sentry-go v0.11.0/go.mod h1:KBQIxiZAetw62Cj8Ri964vAEWVdgfaUCn30Q3bCvANo=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM=
github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98=
github.com/go-chi/chi/v5 v5.0.7 h1:rDTPXLDHGATaeHvVlLcR4Qe0zftYethFucbjVQ1PxU8=
github.com/go-chi/chi/v5 v5.0.7/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w= github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
@ -71,24 +57,13 @@ github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg= github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w= github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo=
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gofiber/fiber/v2 v2.21.0 h1:tdRNrgqWqcHWBwE3o51oAleEVsil4Ro02zd2vMEuP4Q=
github.com/gofiber/fiber/v2 v2.21.0/go.mod h1:MR1usVH3JHYRyQwMe2eZXRSZHRX38fkV+A7CPB+DlDQ=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gojek/heimdall/v7 v7.0.2 h1:+YutGXZ8oEWbCJIwjRnkKmoTl+Oxt1Urs3hc/FR0sxU=
github.com/gojek/heimdall/v7 v7.0.2/go.mod h1:Z43HtMid7ysSjmsedPTXAki6jcdcNVnjn5pmsTyiMic=
github.com/gojek/valkyrie v0.0.0-20180215180059-6aee720afcdf/go.mod h1:QzhUKaYKJmcbTnCYCAVQrroCOY7vOOI8cSQ4NbuhYf0=
github.com/gojek/valkyrie v0.0.0-20190210220504-8f62c1e7ba45 h1:jrnJW3T+GsaQCD26fe6ERlNpgLB5HlekzBU4lOscr80=
github.com/gojek/valkyrie v0.0.0-20190210220504-8f62c1e7ba45/go.mod h1:QzhUKaYKJmcbTnCYCAVQrroCOY7vOOI8cSQ4NbuhYf0=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
@ -99,7 +74,6 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
@ -110,6 +84,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
@ -122,21 +98,13 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/
github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 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 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= 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-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-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk=
github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI=
github.com/jackc/pgconn v1.6.4/go.mod h1:w2pne1C2tZgP+TvjqLpOigGzNqjBgQW9dUw/4Chex78=
github.com/jackc/pgconn v1.7.0/go.mod h1:sF/lPpNEMEOp+IYhyQGdAvrG20gWf6A1tKlr0v7JMeA=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
@ -150,61 +118,37 @@ github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5W
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.0.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.0.5/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI= github.com/jackc/pgproto3/v2 v2.1.1 h1:7PQ/4gLoqnl87ZxL7xjO0DR5gYuviDCZxQJsUlFW1eI=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0=
github.com/jackc/pgtype v1.3.0/go.mod h1:b0JqxHvPmljG+HQ5IsvQ0yqeSi4nGcDTVjFoiLDb0Ik=
github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po=
github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ=
github.com/jackc/pgtype v1.4.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs= github.com/jackc/pgtype v1.8.1 h1:9k0IXtdJXHJbyAWQgbWr1lU+MEhPXZz6RIXxfR5oxXs=
github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx v3.6.2+incompatible h1:2zP5OD7kiyR3xzRYMhOcXVvkDZsImVXfj+yIyTQf3/o=
github.com/jackc/pgx v3.6.2+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA=
github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oAlxAg=
github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o=
github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg=
github.com/jackc/pgx/v4 v4.8.1/go.mod h1:4HOLxrl8wToZJReD04/yB20GDwf4KBYETvlHciCnwW0=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570= github.com/jackc/pgx/v4 v4.13.0 h1:JCjhT5vmhMAf/YwBHLvrBn4OGdIQBiFG6ym8Zmdx570=
github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0= github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.2/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.4 h1:5Ey/o5IfV7dYX6Znivq+N9MdK1S18OJI5OJq6EAAADw= github.com/jackc/puddle v1.1.4 h1:5Ey/o5IfV7dYX6Znivq+N9MdK1S18OJI5OJq6EAAADw=
github.com/jackc/puddle v1.1.4/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.4/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jinzhu/gorm v1.9.12/go.mod h1:vhTjlKSJUTWNtcbQtrMBFCxy7eXTzeCAzfL5fBZT/Qs=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8=
@ -215,20 +159,19 @@ github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubc
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0=
github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
@ -237,10 +180,7 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhR
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.4.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
@ -252,17 +192,24 @@ github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.1+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.35 h1:JuPPxWLdxQmNLSaS8AWZnO5HBadeI1xg6FGrEELQEVU=
github.com/minio/minio-go/v7 v7.0.35/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
@ -290,9 +237,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20= github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20=
github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
@ -301,13 +249,13 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v0.0.0-20200419222939-1884f454f8ea/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
@ -319,8 +267,6 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@ -332,15 +278,10 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/fasthttp v1.31.0 h1:lrauRLII19afgCs2fnWRJ4M5IkV0lo2FqA61uGkNBfE=
github.com/valyala/fasthttp v1.31.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
@ -364,35 +305,28 @@ go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -401,8 +335,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -417,7 +351,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -429,11 +362,12 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211108224332-cbcd623f202e h1:9nbuBbpiqktwdlzHKUohsD5+y2a0QvX98gIWK2ARkqc= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211108224332-cbcd623f202e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -463,8 +397,6 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -475,19 +407,18 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=

View File

@ -1,40 +1,44 @@
package health package health
import ( import (
"context"
"net/http"
"time"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2" "github.com/minio/minio-go/v7"
"github.com/jackc/pgx/v4/pgxpool"
) )
type Dependencies struct { type Dependencies struct {
DB *pgxpool.Pool Bucket *minio.Client
Redis *redis.Client Cache *redis.Client
} }
func (d *Dependencies) Health(c *fiber.Ctx) error { func (d *Dependencies) Health(w http.ResponseWriter, r *http.Request) {
conn, err := d.DB.Acquire(c.Context()) ctx, cancel := context.WithTimeout(r.Context(), time.Second*15)
if err != nil { defer cancel()
return err
}
defer conn.Release()
// Ping REDIS database var bucketOk = true
err = d.Redis.Ping(c.Context()).Err() var cacheOk = true
cancel, err := d.Bucket.HealthCheck(time.Second * 15)
if err != nil { if err != nil {
return c. bucketOk = false
Status(fiber.StatusServiceUnavailable).
JSON(Error{
Error: "REDIS: " + err.Error(),
})
} }
_, err = conn.Query(c.Context(), "SELECT \"id\" FROM \"jokesbapak2\" LIMIT 1") if cancel != nil {
cancel()
}
_, err = d.Cache.Ping(ctx).Result()
if err != nil { if err != nil {
return c. cacheOk = false
Status(fiber.StatusServiceUnavailable).
JSON(Error{
Error: "POSTGRESQL: " + err.Error(),
})
} }
return c.SendStatus(fiber.StatusOK)
if !bucketOk || !cacheOk {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
w.WriteHeader(http.StatusOK)
} }

View File

@ -1,17 +1,13 @@
package joke package joke
import ( import (
"github.com/Masterminds/squirrel"
"github.com/allegro/bigcache/v3" "github.com/allegro/bigcache/v3"
"github.com/go-redis/redis/v8" "github.com/go-redis/redis/v8"
"github.com/gojek/heimdall/v7/httpclient" "github.com/minio/minio-go/v7"
"github.com/jackc/pgx/v4/pgxpool"
) )
type Dependencies struct { type Dependencies struct {
DB *pgxpool.Pool
Redis *redis.Client Redis *redis.Client
Memory *bigcache.BigCache Memory *bigcache.BigCache
HTTP *httpclient.Client Bucket *minio.Client
Query squirrel.StatementBuilderType
} }

63
api/handler/joke/get.go Normal file
View File

@ -0,0 +1,63 @@
package joke
import (
core "jokes-bapak2-api/core/joke"
"net/http"
"strconv"
"github.com/go-chi/chi/v5"
)
func (d *Dependencies) TodayJoke(w http.ResponseWriter, r *http.Request) {
joke, contentType, err := core.GetTodaysJoke(r.Context(), d.Bucket, d.Redis, d.Memory)
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"error":` + strconv.Quote(err.Error()) + `}`))
return
}
w.Header().Set("Content-Type", contentType)
w.WriteHeader(http.StatusOK)
w.Write(joke)
}
func (d *Dependencies) SingleJoke(w http.ResponseWriter, r *http.Request) {
joke, contentType, err := core.GetRandomJoke(r.Context(), d.Bucket, d.Redis, d.Memory)
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"error":` + strconv.Quote(err.Error()) + `}`))
return
}
w.Header().Set("Content-Type", contentType)
w.WriteHeader(http.StatusOK)
w.Write(joke)
}
func (d *Dependencies) JokeByID(w http.ResponseWriter, r *http.Request) {
id := chi.URLParamFromCtx(r.Context(), "id")
// Parse id to int
parsedId, err := strconv.Atoi(id)
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(`{"error":` + strconv.Quote(err.Error()) + `}`))
return
}
joke, contentType, err := core.GetJokeById(r.Context(), d.Bucket, d.Redis, d.Memory, parsedId)
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"error":` + strconv.Quote(err.Error()) + `}`))
return
}
w.Header().Set("Content-Type", contentType)
w.WriteHeader(http.StatusOK)
w.Write(joke)
}

View File

@ -1,69 +0,0 @@
package joke
import (
core "jokes-bapak2-api/core/joke"
"jokes-bapak2-api/core/schema"
"jokes-bapak2-api/core/validator"
"github.com/gofiber/fiber/v2"
)
func (d *Dependencies) AddNewJoke(c *fiber.Ctx) error {
var body 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",
})
}
validateLink, err := validator.JokeLinkExists(d.DB, c.Context(), body.Link)
if err != nil {
return err
}
if validateLink {
return c.Status(fiber.StatusConflict).JSON(Error{
Error: "Given link is already on the jokesbapak2 database",
})
}
err = core.InsertJokeIntoDB(
d.DB,
c.Context(),
schema.Joke{
Link: body.Link,
Creator: c.Locals("userID").(int),
},
)
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.StatusCreated).
JSON(ResponseJoke{
Link: body.Link,
})
}

View File

@ -1,51 +0,0 @@
package joke
import (
core "jokes-bapak2-api/core/joke"
"jokes-bapak2-api/core/validator"
"strconv"
"github.com/gofiber/fiber/v2"
)
func (d *Dependencies) DeleteJoke(c *fiber.Ctx) error {
id, err := strconv.Atoi(c.Params("id"))
if err != nil {
return err
}
validate, err := validator.JokeIDExists(d.DB, c.Context(), id)
if err != nil {
return err
}
if validate {
return c.
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.StatusOK).
JSON(ResponseJoke{
Message: "specified joke id has been deleted",
})
}

View File

@ -1,151 +0,0 @@
package joke
import (
"errors"
"io/ioutil"
core "jokes-bapak2-api/core/joke"
"jokes-bapak2-api/core/schema"
"jokes-bapak2-api/utils"
"strconv"
"time"
"github.com/gofiber/fiber/v2"
)
func (d *Dependencies) TodayJoke(c *fiber.Ctx) error {
// check from handler.Redis if today's joke already exists
// send the joke if exists
// get a new joke if it's not, then send it.
var joke Today
err := d.Redis.MGet(c.Context(), "today:link", "today:date", "today:image", "today:contentType").Scan(&joke)
if err != nil {
return err
}
eq, err := utils.IsToday(joke.Date)
if err != nil {
return err
}
if eq {
c.Set("Content-Type", joke.ContentType)
return c.Status(fiber.StatusOK).Send([]byte(joke.Image))
}
link, err := core.GetRandomJokeFromDB(d.DB, c.Context())
if err != nil {
return err
}
response, err := d.HTTP.Get(link, nil)
if err != nil {
return err
}
data, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
}
now := time.Now().UTC().Format(time.RFC3339)
err = d.Redis.MSet(c.Context(), map[string]interface{}{
"today:link": link,
"today:date": now,
"today:image": string(data),
"today:contentType": response.Header.Get("content-type"),
}).Err()
if err != nil {
return err
}
c.Set("Content-Type", response.Header.Get("content-type"))
return c.Status(fiber.StatusOK).Send(data)
}
func (d *Dependencies) SingleJoke(c *fiber.Ctx) error {
checkCache, err := core.CheckJokesCache(d.Memory)
if err != nil {
return err
}
if !checkCache {
jokes, err := core.GetAllJSONJokes(d.DB, c.Context())
if err != nil {
return err
}
err = d.Memory.Set("jokes", jokes)
if err != nil {
return err
}
}
link, err := core.GetRandomJokeFromCache(d.Memory)
if err != nil && !errors.Is(err, schema.ErrEmpty) {
return err
}
// Get image data
response, err := d.HTTP.Get(link, nil)
if err != nil {
return err
}
data, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
}
c.Set("Content-Type", response.Header.Get("content-type"))
return c.Status(fiber.StatusOK).Send(data)
}
func (d *Dependencies) JokeByID(c *fiber.Ctx) error {
checkCache, err := core.CheckJokesCache(d.Memory)
if err != nil {
return err
}
if !checkCache {
jokes, err := core.GetAllJSONJokes(d.DB, c.Context())
if err != nil {
return err
}
err = d.Memory.Set("jokes", jokes)
if err != nil {
return err
}
}
id, err := strconv.Atoi(c.Params("id"))
if err != nil {
return err
}
link, err := core.GetCachedJokeByID(d.Memory, id)
if err != nil {
return err
}
if link == "" {
return c.
Status(fiber.StatusNotFound).
Send([]byte("Requested ID was not found."))
}
// Get image data
response, err := d.HTTP.Get(link, nil)
if err != nil {
return err
}
data, err := ioutil.ReadAll(response.Body)
if err != nil {
return err
}
c.Set("Content-Type", response.Header.Get("content-type"))
return c.Status(fiber.StatusOK).Send(data)
}

View File

@ -1,42 +0,0 @@
package joke
import (
"errors"
core "jokes-bapak2-api/core/joke"
"github.com/allegro/bigcache/v3"
"github.com/gofiber/fiber/v2"
)
func (d *Dependencies) TotalJokes(c *fiber.Ctx) error {
checkTotal, err := core.CheckTotalJokesCache(d.Memory)
if err != nil {
return err
}
if !checkTotal {
err = core.SetTotalJoke(d.DB, c.Context(), d.Memory)
if err != nil {
return err
}
}
total, err := d.Memory.Get("total")
if err != nil {
if errors.Is(err, bigcache.ErrEntryNotFound) {
return c.
Status(fiber.StatusInternalServerError).
JSON(Error{
Error: "no data found",
})
}
return err
}
return c.
Status(fiber.StatusOK).
JSON(ResponseJoke{
Message: string(total),
})
}

View File

@ -1,86 +0,0 @@
package joke
import (
core "jokes-bapak2-api/core/joke"
"jokes-bapak2-api/core/schema"
"jokes-bapak2-api/core/validator"
"strconv"
"github.com/gofiber/fiber/v2"
)
func (d *Dependencies) UpdateJoke(c *fiber.Ctx) error {
id := c.Params("id")
// Check if the joke exists
jokeExists, err := core.CheckJokeExists(d.DB, c.Context(), id)
if err != nil {
return err
}
if !jokeExists {
return c.
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",
})
}
newID, err := strconv.Atoi(id)
if err != nil {
return err
}
newCreator, err := strconv.Atoi(c.Locals("userID").(string))
if err != nil {
return err
}
updatedJoke := schema.Joke{
Link: body.Link,
Creator: newCreator,
ID: newID,
}
err = core.UpdateJoke(d.DB, c.Context(), updatedJoke)
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.StatusOK).
JSON(ResponseJoke{
Message: "specified joke id has been updated",
Link: body.Link,
})
}

21
api/handler/joke/total.go Normal file
View File

@ -0,0 +1,21 @@
package joke
import (
core "jokes-bapak2-api/core/joke"
"net/http"
"strconv"
)
func (d *Dependencies) TotalJokes(w http.ResponseWriter, r *http.Request) {
total, err := core.GetTotalJoke(r.Context(), d.Bucket, d.Redis, d.Memory)
if err != nil {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(`{"error":` + strconv.Quote(err.Error()) + `}`))
return
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message":` + strconv.Itoa(total) + `}`))
}

View File

@ -1,17 +0,0 @@
package submit
import (
"github.com/Masterminds/squirrel"
"github.com/allegro/bigcache/v3"
"github.com/go-redis/redis/v8"
"github.com/gojek/heimdall/v7/httpclient"
"github.com/jackc/pgx/v4/pgxpool"
)
type Dependencies struct {
DB *pgxpool.Pool
Redis *redis.Client
Memory *bigcache.BigCache
HTTP *httpclient.Client
Query squirrel.StatementBuilderType
}

View File

@ -1,99 +0,0 @@
package submit
import (
"jokes-bapak2-api/core/schema"
core "jokes-bapak2-api/core/submit"
"jokes-bapak2-api/core/validator"
"net/url"
"strings"
"github.com/gofiber/fiber/v2"
)
func (d *Dependencies) SubmitJoke(c *fiber.Ctx) error {
conn, err := d.DB.Acquire(c.Context())
if err != nil {
return err
}
defer conn.Release()
var body schema.Submission
err = c.BodyParser(&body)
if err != nil {
return err
}
// Image and/or Link should not be empty
if body.Image == "" && body.Link == "" {
return c.Status(fiber.StatusBadRequest).JSON(schema.Error{
Error: "A link or an image should be supplied in a form of multipart/form-data",
})
}
// Author should be supplied
if body.Author == "" {
return c.Status(fiber.StatusBadRequest).JSON(schema.Error{
Error: "An author key consisting on the format \"yourname <youremail@mail>\" must be supplied",
})
} else {
// Validate format
valid := validator.ValidateAuthor(body.Author)
if !valid {
return c.Status(fiber.StatusBadRequest).JSON(schema.Error{
Error: "Please stick to the format of \"yourname <youremail@mail>\" and within 200 characters",
})
}
}
var link string
// Check link validity if link was provided
if body.Link != "" {
valid, err := validator.CheckImageValidity(d.HTTP, body.Link)
if err != nil {
return err
}
if !valid {
return c.Status(fiber.StatusBadRequest).JSON(schema.Error{
Error: "URL provided is not a valid image",
})
}
link = body.Link
}
// If image was provided
if body.Image != "" {
image := strings.NewReader(body.Image)
link, err = core.UploadImage(d.HTTP, image)
if err != nil {
return err
}
}
// Validate if link already exists
validateLink, err := validator.SubmitLinkExists(d.DB, c.Context(), d.Query, link)
if err != nil {
return err
}
if validateLink {
return c.Status(fiber.StatusConflict).JSON(schema.Error{
Error: "Given link is already on the submission queue.",
})
}
submission, err := core.SubmitJoke(d.DB, c.Context(), body, link)
if err != nil {
return err
}
return c.
Status(fiber.StatusCreated).
JSON(schema.ResponseSubmission{
Message: "Joke submitted. Please wait for a few days for admin to approve your submission.",
Submission: submission,
AuthorPage: "/submit?author=" + url.QueryEscape(body.Author),
})
}

View File

@ -1,28 +0,0 @@
package submit
import (
"jokes-bapak2-api/core/schema"
core "jokes-bapak2-api/core/submit"
"github.com/gofiber/fiber/v2"
)
func (d *Dependencies) GetSubmission(c *fiber.Ctx) error {
query := new(schema.SubmissionQuery)
err := c.QueryParser(query)
if err != nil {
return err
}
submissions, err := core.GetSubmittedItems(d.DB, c.Context(), *query)
if err != nil {
return err
}
return c.
Status(fiber.StatusOK).
JSON(fiber.Map{
"count": len(submissions),
"jokes": submissions,
})
}

View File

@ -1,55 +1,72 @@
package main package main
import ( import (
"errors"
"log" "log"
"net/http"
"os" "os"
"os/signal" "os/signal"
"context" "context"
"jokes-bapak2-api/core/joke" "jokes-bapak2-api/core/joke"
"jokes-bapak2-api/platform/database"
"jokes-bapak2-api/routes" "jokes-bapak2-api/routes"
"github.com/go-redis/redis/v8"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
"time" "time"
"github.com/gofiber/fiber/v2"
_ "github.com/joho/godotenv/autoload"
"github.com/Masterminds/squirrel"
"github.com/allegro/bigcache/v3" "github.com/allegro/bigcache/v3"
"github.com/getsentry/sentry-go" "github.com/getsentry/sentry-go"
"github.com/go-redis/redis/v8" "github.com/go-chi/chi/v5"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/etag"
"github.com/gofiber/fiber/v2/middleware/limiter"
"github.com/gojek/heimdall/v7/httpclient"
"github.com/jackc/pgx/v4/pgxpool"
) )
func main() { func main() {
// Setup PostgreSQL redisUrl, ok := os.LookupEnv("REDIS_URL")
poolConfig, err := pgxpool.ParseConfig(os.Getenv("DATABASE_URL")) if !ok {
if err != nil { redisUrl = "redis://@localhost:6379"
log.Panicln("Unable to create pool config", err)
} }
poolConfig.MaxConnIdleTime = time.Minute * 3
poolConfig.MaxConnLifetime = time.Minute * 5
poolConfig.MaxConns = 15
poolConfig.MinConns = 4
db, err := pgxpool.ConnectConfig(context.Background(), poolConfig) minioHost, ok := os.LookupEnv("MINIO_HOST")
if err != nil { if !ok {
log.Panicln("Unable to create connection", err) minioHost = "localhost:9000"
} }
defer db.Close()
// Setup Redis minioID, ok := os.LookupEnv("MINIO_ACCESS_ID")
opt, err := redis.ParseURL(os.Getenv("REDIS_URL")) if !ok {
if err != nil { minioID = "minio"
log.Fatalln(err) }
minioSecret, ok := os.LookupEnv("MINIO_SECRET_KEY")
if !ok {
minioSecret = "password"
}
minioToken, ok := os.LookupEnv("MINIO_TOKEN")
if !ok {
minioToken = ""
}
sentryDsn, ok := os.LookupEnv("SENTRY_DSN")
if !ok {
sentryDsn = ""
}
port, ok := os.LookupEnv("PORT")
if !ok {
port = "5000"
}
hostname, ok := os.LookupEnv("HOSTNAME")
if !ok {
hostname = "127.0.0.1"
}
environment, ok := os.LookupEnv("ENVIRONMENT")
if !ok {
environment = "development"
} }
rdb := redis.NewClient(opt)
defer rdb.Close()
// Setup In Memory // Setup In Memory
memory, err := bigcache.NewBigCache(bigcache.DefaultConfig(6 * time.Hour)) memory, err := bigcache.NewBigCache(bigcache.DefaultConfig(6 * time.Hour))
@ -58,122 +75,87 @@ func main() {
} }
defer memory.Close() defer memory.Close()
// Setup MinIO
minioClient, err := minio.New(minioHost, &minio.Options{
Creds: credentials.NewStaticV4(minioID, minioSecret, minioToken),
})
if err != nil {
log.Fatalf("setting up minio client: %s", err.Error())
return
}
parsedRedisUrl, err := redis.ParseURL(redisUrl)
if err != nil {
log.Fatalf("parsing redis url: %s", err.Error())
return
}
redisClient := redis.NewClient(parsedRedisUrl)
defer func() {
err := redisClient.Close()
if err != nil {
log.Printf("closing redis client: %s", err.Error())
}
}()
// Setup Sentry // Setup Sentry
err = sentry.Init(sentry.ClientOptions{ err = sentry.Init(sentry.ClientOptions{
Dsn: os.Getenv("SENTRY_DSN"), Dsn: sentryDsn,
Environment: os.Getenv("ENV"), Environment: environment,
AttachStacktrace: true, AttachStacktrace: true,
// Enable printing of SDK debug messages. // Enable printing of SDK debug messages.
// Useful when getting started or trying to figure something out. // Useful when getting started or trying to figure something out.
Debug: true, Debug: environment != "production",
}) })
if err != nil { if err != nil {
log.Panicln(err) log.Fatalf("setting up sentry: %s", err.Error())
return
} }
defer sentry.Flush(2 * time.Second) defer sentry.Flush(2 * time.Second)
setupCtx, setupCancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute*4)) setupCtx, setupCancel := context.WithDeadline(context.Background(), time.Now().Add(time.Minute*4))
defer setupCancel() defer setupCancel()
err = database.Populate(db, setupCtx) _, _, err = joke.GetTodaysJoke(setupCtx, minioClient, redisClient, memory)
if err != nil { if err != nil {
sentry.CaptureException(err) log.Fatalf("getting initial joke data: %s", err.Error())
log.Panicln(err) return
} }
err = joke.SetAllJSONJoke(db, setupCtx, memory) healthRouter := routes.Health(minioClient, redisClient)
if err != nil { jokeRouter := routes.Joke(minioClient, redisClient, memory)
log.Panicln(err)
} router := chi.NewRouter()
err = joke.SetTotalJoke(db, setupCtx, memory)
if err != nil { router.Mount("/health", healthRouter)
log.Panicln(err) router.Mount("/", jokeRouter)
server := &http.Server{
Handler: router,
Addr: hostname + ":" + port,
ReadTimeout: time.Minute,
WriteTimeout: time.Minute,
IdleTimeout: time.Second * 30,
ReadHeaderTimeout: time.Minute,
} }
timeoutDefault := time.Minute * 1 exitSignal := make(chan os.Signal, 1)
signal.Notify(exitSignal, os.Interrupt)
app := fiber.New(fiber.Config{
ReadTimeout: timeoutDefault,
WriteTimeout: timeoutDefault,
CaseSensitive: true,
DisableKeepalive: true,
ErrorHandler: errorHandler,
})
app.Use(limiter.New(limiter.Config{
Max: 30,
Expiration: 1 * time.Minute,
LimitReached: limitHandler,
}))
app.Use(cors.New())
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,
}
route.Health()
route.Joke()
route.Submit()
// Start server (with or without graceful shutdown).
if os.Getenv("ENV") == "development" {
StartServer(app)
} else {
StartServerWithGracefulShutdown(app)
}
}
func limitHandler(c *fiber.Ctx) error {
return c.Status(fiber.StatusTooManyRequests).JSON(fiber.Map{
"message": "we only allow up to 15 request per minute",
})
}
func errorHandler(c *fiber.Ctx, err error) error {
log.Println(err)
sentry.CaptureException(err)
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
"error": "Something went wrong on our end",
})
}
// StartServerWithGracefulShutdown function for starting server with a graceful shutdown.
func StartServerWithGracefulShutdown(a *fiber.App) {
// Create channel for idle connections.
idleConnsClosed := make(chan struct{})
go func() { go func() {
sigint := make(chan os.Signal, 1) err := server.ListenAndServe()
signal.Notify(sigint, os.Interrupt) // Catch OS signals. if err != nil && !errors.Is(err, http.ErrServerClosed) {
<-sigint log.Fatalf("listening http server: %v", err)
// Received an interrupt signal, shutdown.
if err := a.Shutdown(); err != nil {
// Error from closing listeners, or context timeout:
log.Printf("Oops... Server is not shutting down! Reason: %v", err)
} }
close(idleConnsClosed)
}() }()
// Run server. <-exitSignal
if err := a.Listen(os.Getenv("HOST") + ":" + os.Getenv("PORT")); err != nil {
log.Printf("Oops... Server is not running! Reason: %v", err)
}
<-idleConnsClosed shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), time.Second*30)
} defer shutdownCancel()
// StartServer func for starting a simple server. err = server.Shutdown(shutdownCtx)
func StartServer(a *fiber.App) { if err != nil {
// Run server. log.Printf("shutting down http server: %v", err)
if err := a.Listen(os.Getenv("HOST") + ":" + os.Getenv("PORT")); err != nil {
log.Printf("Oops... Server is not running! Reason: %v", err)
} }
} }

View File

@ -1,58 +0,0 @@
package middleware
import (
"jokes-bapak2-api/core/administrator"
phccrypto "github.com/aldy505/phc-crypto"
"github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v4/pgxpool"
)
func RequireAuth(db *pgxpool.Pool) fiber.Handler {
return func(c *fiber.Ctx) error {
var auth Auth
err := c.BodyParser(&auth)
if err != nil {
return err
}
token, err := administrator.CheckKeyExists(db, c.Context(), auth.Key)
if err != nil {
return err
}
if token == "" {
return c.
Status(fiber.StatusForbidden).
JSON(Error{
Error: "Invalid key",
})
}
crypto, err := phccrypto.Use(phccrypto.Argon2, phccrypto.Config{})
if err != nil {
return err
}
verify, err := crypto.Verify(token, auth.Token)
if err != nil {
return err
}
if verify {
id, err := administrator.GetUserID(db, c.Context(), auth.Key)
if err != nil {
return err
}
c.Locals("userID", id)
return c.Next()
}
return c.
Status(fiber.StatusForbidden).
JSON(Error{
Error: "Invalid key",
})
}
}

View File

@ -1,12 +0,0 @@
package middleware
type Auth struct {
ID int `json:"id" form:"id" db:"id"`
Key string `json:"key" form:"key" db:"key"`
Token string `json:"token" form:"token" db:"token"`
LastUsed string `json:"last_used" form:"last_used" db:"last_used"`
}
type Error struct {
Error string `json:"error"`
}

View File

@ -1,27 +0,0 @@
package middleware
import (
"regexp"
"github.com/gofiber/fiber/v2"
)
func OnlyIntegerAsID() fiber.Handler {
return func(c *fiber.Ctx) error {
regex, err := regexp.Compile(`([0-9]+)`)
if err != nil {
return err
}
loc := regex.FindStringIndex(c.Params("id"))
if loc[1] == len(c.Params("id")) {
return c.Next()
}
return c.
Status(fiber.StatusBadRequest).
JSON(Error{
Error: "only numbers are allowed as ID",
})
}
}

View File

@ -1,19 +0,0 @@
package routes
import (
"github.com/Masterminds/squirrel"
"github.com/allegro/bigcache/v3"
"github.com/go-redis/redis/v8"
"github.com/gofiber/fiber/v2"
"github.com/gojek/heimdall/v7/httpclient"
"github.com/jackc/pgx/v4/pgxpool"
)
type Dependencies struct {
DB *pgxpool.Pool
Redis *redis.Client
Memory *bigcache.BigCache
HTTP *httpclient.Client
Query squirrel.StatementBuilderType
App *fiber.App
}

View File

@ -2,18 +2,21 @@ package routes
import ( import (
"jokes-bapak2-api/handler/health" "jokes-bapak2-api/handler/health"
"time"
"github.com/gofiber/fiber/v2/middleware/cache" "github.com/go-chi/chi/v5"
"github.com/go-redis/redis/v8"
"github.com/minio/minio-go/v7"
) )
func (d *Dependencies) Health() { func Health(bucket *minio.Client, cache *redis.Client) *chi.Mux {
// Health check dependency := &health.Dependencies{
deps := health.Dependencies{ Bucket: bucket,
DB: d.DB, Cache: cache,
Redis: d.Redis,
} }
d.App.Get("/health", cache.New(cache.Config{Expiration: 30 * time.Minute}), deps.Health) router := chi.NewRouter()
d.App.Get("/v1/health", cache.New(cache.Config{Expiration: 30 * time.Minute}), deps.Health)
router.Get("/", dependency.Health)
return router
} }

View File

@ -2,42 +2,33 @@ package routes
import ( import (
"jokes-bapak2-api/handler/joke" "jokes-bapak2-api/handler/joke"
"jokes-bapak2-api/middleware"
"time"
"github.com/gofiber/fiber/v2/middleware/cache" "github.com/allegro/bigcache/v3"
"github.com/go-chi/chi/v5"
"github.com/go-redis/redis/v8"
"github.com/minio/minio-go/v7"
) )
func (d *Dependencies) Joke() { func Joke(bucket *minio.Client, cache *redis.Client, memory *bigcache.BigCache) *chi.Mux {
deps := joke.Dependencies{ deps := &joke.Dependencies{
DB: d.DB, Memory: memory,
Redis: d.Redis, Bucket: bucket,
Memory: d.Memory, Redis: cache,
HTTP: d.HTTP,
Query: d.Query,
} }
router := chi.NewRouter()
// Single route // Single route
d.App.Get("/", deps.SingleJoke) router.Get("/", deps.SingleJoke)
d.App.Get("/v1", deps.SingleJoke)
// Today's joke // Today's joke
d.App.Get("/today", cache.New(cache.Config{Expiration: 6 * time.Hour}), deps.TodayJoke) router.Get("/today", deps.TodayJoke)
d.App.Get("/v1/today", cache.New(cache.Config{Expiration: 6 * time.Hour}), deps.TodayJoke)
// Joke by ID // Joke by ID
d.App.Get("/id/:id", middleware.OnlyIntegerAsID(), deps.JokeByID) router.Get("/id/{id}", deps.JokeByID)
d.App.Get("/v1/id/:id", middleware.OnlyIntegerAsID(), deps.JokeByID)
// Count total jokes // Count total jokes
d.App.Get("/total", cache.New(cache.Config{Expiration: 15 * time.Minute}), deps.TotalJokes) router.Get("/total", deps.TotalJokes)
d.App.Get("/v1/total", cache.New(cache.Config{Expiration: 15 * time.Minute}), deps.TotalJokes)
// Add new joke return router
d.App.Put("/", middleware.RequireAuth(d.DB), deps.AddNewJoke)
// Update a joke
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), middleware.OnlyIntegerAsID(), deps.DeleteJoke)
} }

View File

@ -1,33 +0,0 @@
package routes
import (
"jokes-bapak2-api/handler/submit"
"time"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cache"
)
func (d *Dependencies) Submit() {
deps := submit.Dependencies{
DB: d.DB,
Redis: d.Redis,
Memory: d.Memory,
HTTP: d.HTTP,
Query: d.Query,
}
// Get pending submitted joke
d.App.Get(
"/submit",
cache.New(cache.Config{
Expiration: 5 * time.Minute,
KeyGenerator: func(c *fiber.Ctx) string {
return c.OriginalURL()
},
}),
deps.GetSubmission)
// Add a joke
d.App.Post("/submit", deps.SubmitJoke)
}

BIN
api/samples/sample1.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
api/samples/sample2.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

BIN
api/samples/sample3.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
api/samples/sample4.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
api/samples/sample5.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB