feat: rewritten the most part of it
This commit is contained in:
parent
215a160205
commit
3d7e1dd077
|
@ -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" ]
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package joke
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const JokesBapak2Bucket = "jokesbapak2"
|
||||||
|
|
||||||
|
type Joke struct {
|
||||||
|
FileName string
|
||||||
|
ContentType string
|
||||||
|
ModifiedAt time.Time
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
|
@ -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")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
44
api/go.mod
44
api/go.mod
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
141
api/go.sum
141
api/go.sum
|
@ -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=
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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",
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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),
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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) + `}`))
|
||||||
|
}
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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),
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
})
|
|
||||||
}
|
|
224
api/main.go
224
api/main.go
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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"`
|
|
||||||
}
|
|
|
@ -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",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
Binary file not shown.
After Width: | Height: | Size: 92 KiB |
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
Binary file not shown.
After Width: | Height: | Size: 105 KiB |
Loading…
Reference in New Issue