refactor(db): migrate to pgx v5

This commit is contained in:
Yanislav Igonin 2026-07-15 23:24:28 +04:00
parent 94adc28e65
commit 2b98b9a55a
10 changed files with 207 additions and 274 deletions

View File

@ -1,7 +1,7 @@
package controllers package controllers
import ( import (
"context" "errors"
"log" "log"
"math" "math"
"path/filepath" "path/filepath"
@ -10,6 +10,7 @@ import (
"github.com/dchest/captcha" "github.com/dchest/captcha"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/jackc/pgx/v5"
"micrach/config" "micrach/config"
"micrach/db" "micrach/db"
@ -18,6 +19,7 @@ import (
) )
func GetThreads(c *fiber.Ctx) error { func GetThreads(c *fiber.Ctx) error {
ctx := c.UserContext()
pageString := c.Query("page", "1") pageString := c.Query("page", "1")
page, err := strconv.Atoi(pageString) page, err := strconv.Atoi(pageString)
if err != nil { if err != nil {
@ -30,12 +32,12 @@ func GetThreads(c *fiber.Ctx) error {
limit := 10 limit := 10
offset := limit * (page - 1) offset := limit * (page - 1)
threads, err := repositories.Posts.Get(limit, offset) threads, err := repositories.Posts.Get(ctx, limit, offset)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
} }
count, err := repositories.Posts.GetThreadsCount() count, err := repositories.Posts.GetThreadsCount(ctx)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
@ -62,11 +64,12 @@ func GetThreads(c *fiber.Ctx) error {
} }
func GetThread(c *fiber.Ctx) error { func GetThread(c *fiber.Ctx) error {
ctx := c.UserContext()
threadID, err := c.ParamsInt("threadID") threadID, err := c.ParamsInt("threadID")
if err != nil { if err != nil {
return c.Status(fiber.StatusNotFound).Render("pages/404", nil) return c.Status(fiber.StatusNotFound).Render("pages/404", nil)
} }
thread, err := repositories.Posts.GetThreadByPostID(threadID) thread, err := repositories.Posts.GetThreadByPostID(ctx, threadID)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
@ -89,6 +92,7 @@ func GetThread(c *fiber.Ctx) error {
} }
func CreateThread(c *fiber.Ctx) error { func CreateThread(c *fiber.Ctx) error {
ctx := c.UserContext()
form, err := c.MultipartForm() form, err := c.MultipartForm()
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
@ -119,38 +123,43 @@ func CreateThread(c *fiber.Ctx) error {
} }
} }
conn, err := db.Pool.Acquire(context.TODO()) conn, err := db.Pool.Acquire(ctx)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
} }
defer conn.Release() defer conn.Release()
threadsCount, err := repositories.Posts.GetThreadsCount() threadsCount, err := repositories.Posts.GetThreadsCount(ctx)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
} }
if threadsCount >= config.App.ThreadsMaxCount { if threadsCount >= config.App.ThreadsMaxCount {
oldestThreadUpdatedAt, err := repositories.Posts.GetOldestThreadUpdatedAt() oldestThreadUpdatedAt, err := repositories.Posts.GetOldestThreadUpdatedAt(ctx)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
} }
err = repositories.Posts.ArchiveThreadsFrom(oldestThreadUpdatedAt) err = repositories.Posts.ArchiveThreadsFrom(ctx, oldestThreadUpdatedAt)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
} }
} }
tx, err := conn.Begin(context.TODO()) tx, err := conn.Begin(ctx)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
} }
defer tx.Rollback(context.TODO()) defer func() {
rollbackErr := tx.Rollback(ctx)
if rollbackErr != nil && !errors.Is(rollbackErr, pgx.ErrTxClosed) {
log.Println("error:", rollbackErr)
}
}()
post := repositories.Post{ post := repositories.Post{
IsParent: true, IsParent: true,
@ -158,7 +167,7 @@ func CreateThread(c *fiber.Ctx) error {
Text: text, Text: text,
IsSage: false, IsSage: false,
} }
threadID, err := repositories.Posts.CreateInTx(tx, post) threadID, err := repositories.Posts.CreateInTx(ctx, tx, post)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
@ -179,7 +188,7 @@ func CreateThread(c *fiber.Ctx) error {
Size: int(fileInRequest.Size), Size: int(fileInRequest.Size),
} }
fileID, err := repositories.Files.CreateInTx(tx, file) fileID, err := repositories.Files.CreateInTx(ctx, tx, file)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
@ -210,7 +219,10 @@ func CreateThread(c *fiber.Ctx) error {
} }
} }
tx.Commit(context.TODO()) if err := tx.Commit(ctx); err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
}
path := "/" + strconv.Itoa(threadID) path := "/" + strconv.Itoa(threadID)
return c.Redirect(path, fiber.StatusFound) return c.Redirect(path, fiber.StatusFound)
@ -218,12 +230,13 @@ func CreateThread(c *fiber.Ctx) error {
// Add new post in thread // Add new post in thread
func UpdateThread(c *fiber.Ctx) error { func UpdateThread(c *fiber.Ctx) error {
ctx := c.UserContext()
threadID, err := c.ParamsInt("threadID") threadID, err := c.ParamsInt("threadID")
if err != nil { if err != nil {
return c.Status(fiber.StatusNotFound).Render("pages/404", nil) return c.Status(fiber.StatusNotFound).Render("pages/404", nil)
} }
isArchived, err := repositories.Posts.GetIfThreadIsArchived(threadID) isArchived, err := repositories.Posts.GetIfThreadIsArchived(ctx, threadID)
if isArchived { if isArchived {
errorHtmlData := repositories.BadRequestHtmlData{ errorHtmlData := repositories.BadRequestHtmlData{
Message: repositories.ThreadIsArchivedErrorMessage, Message: repositories.ThreadIsArchivedErrorMessage,
@ -271,7 +284,7 @@ func UpdateThread(c *fiber.Ctx) error {
} }
isSage := isSageString == "on" isSage := isSageString == "on"
conn, err := db.Pool.Acquire(context.TODO()) conn, err := db.Pool.Acquire(ctx)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
@ -279,13 +292,18 @@ func UpdateThread(c *fiber.Ctx) error {
} }
defer conn.Release() defer conn.Release()
tx, err := conn.Begin(context.TODO()) tx, err := conn.Begin(ctx)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
} }
defer tx.Rollback(context.TODO()) defer func() {
rollbackErr := tx.Rollback(ctx)
if rollbackErr != nil && !errors.Is(rollbackErr, pgx.ErrTxClosed) {
log.Println("error:", rollbackErr)
}
}()
post := repositories.Post{ post := repositories.Post{
IsParent: false, IsParent: false,
@ -294,14 +312,14 @@ func UpdateThread(c *fiber.Ctx) error {
Text: text, Text: text,
IsSage: isSage, IsSage: isSage,
} }
postID, err := repositories.Posts.CreateInTx(tx, post) postID, err := repositories.Posts.CreateInTx(ctx, tx, post)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
} }
postsCountInThread, err := repositories.Posts.GetThreadPostsCount(threadID) postsCountInThread, err := repositories.Posts.GetThreadPostsCount(ctx, threadID)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
@ -310,7 +328,7 @@ func UpdateThread(c *fiber.Ctx) error {
isBumpLimit := postsCountInThread >= config.App.ThreadBumpLimit isBumpLimit := postsCountInThread >= config.App.ThreadBumpLimit
isThreadBumped := !isBumpLimit && !isSage && !post.IsParent isThreadBumped := !isBumpLimit && !isSage && !post.IsParent
if isThreadBumped { if isThreadBumped {
err = repositories.Posts.BumpThreadInTx(tx, threadID) err = repositories.Posts.BumpThreadInTx(ctx, tx, threadID)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
@ -327,7 +345,7 @@ func UpdateThread(c *fiber.Ctx) error {
Size: int(fileInRequest.Size), Size: int(fileInRequest.Size),
} }
fileID, err := repositories.Files.CreateInTx(tx, file) fileID, err := repositories.Files.CreateInTx(ctx, tx, file)
if err != nil { if err != nil {
log.Println("error:", err) log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil) return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
@ -362,7 +380,10 @@ func UpdateThread(c *fiber.Ctx) error {
} }
} }
tx.Commit(context.TODO()) if err := tx.Commit(ctx); err != nil {
log.Println("error:", err)
return c.Status(fiber.StatusInternalServerError).Render("pages/500", nil)
}
path := "/" + strconv.Itoa(threadID) + "#" + strconv.Itoa(postID) path := "/" + strconv.Itoa(threadID) + "#" + strconv.Itoa(postID)
return c.Redirect(path) return c.Redirect(path)

View File

@ -2,6 +2,7 @@ package db
import ( import (
"context" "context"
"errors"
"log" "log"
"path/filepath" "path/filepath"
"strconv" "strconv"
@ -10,7 +11,8 @@ import (
Config "micrach/config" Config "micrach/config"
Files "micrach/files" Files "micrach/files"
"github.com/jackc/pgx/v4/pgxpool" "github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgxpool"
) )
var Pool *pgxpool.Pool var Pool *pgxpool.Pool
@ -21,9 +23,9 @@ type Migration struct {
Name string Name string
} }
func Init() { func Init(ctx context.Context) {
var err error var err error
Pool, err = pgxpool.Connect(context.TODO(), Config.Db.Url) Pool, err = pgxpool.New(ctx, Config.Db.Url)
if err != nil { if err != nil {
log.Println("database - offline") log.Println("database - offline")
log.Panicln(err) log.Panicln(err)
@ -32,8 +34,8 @@ func Init() {
log.Println("database - online") log.Println("database - online")
} }
func Migrate() { func Migrate(ctx context.Context) {
dbMigrations := getDbMigrations() dbMigrations := getDbMigrations(ctx)
sqlMigrations := Files.GetFullFilePathsInFolder("migrations") sqlMigrations := Files.GetFullFilePathsInFolder("migrations")
for _, m := range sqlMigrations { for _, m := range sqlMigrations {
filename := filepath.Base(m) filename := filepath.Base(m)
@ -48,7 +50,7 @@ func Migrate() {
_, isMigrationInDb := dbMigrations[id] _, isMigrationInDb := dbMigrations[id]
if !isMigrationInDb { if !isMigrationInDb {
sql := Files.ReadFileText(m) sql := Files.ReadFileText(m)
runMigration(id, name, sql) runMigration(ctx, id, name, sql)
log.Println("migration - " + name + " - online") log.Println("migration - " + name + " - online")
} }
} }
@ -56,29 +58,27 @@ func Migrate() {
log.Println("migrations - online") log.Println("migrations - online")
} }
func runMigration(mid int, mname, msql string) { func runMigration(ctx context.Context, mid int, mname, msql string) {
_, err := Pool.Exec(context.TODO(), msql) _, err := Pool.Exec(ctx, msql)
if err != nil { if err != nil {
log.Panicln(err) log.Panicln(err)
} }
sql := `INSERT INTO migrations (id, name) VALUES ($1, $2)` _, err = Pool.Exec(ctx, `INSERT INTO migrations (id, name) VALUES ($1, $2)`, mid, mname)
_, err = Pool.Query(context.TODO(), sql, mid, mname)
if err != nil { if err != nil {
log.Panicln(err) log.Panicln(err)
} }
} }
func getDbMigrations() MigrationsMap { func getDbMigrations(ctx context.Context) MigrationsMap {
sql := `SELECT id, name FROM migrations` rows, err := Pool.Query(ctx, `SELECT id, name FROM migrations`)
rows, err := Pool.Query(context.TODO(), sql) if err != nil {
if err != nil && isNotNonExistentMigrationsTable(err) { if isUndefinedTable(err) {
return make(MigrationsMap)
}
log.Panicln(err) log.Panicln(err)
} }
defer rows.Close()
if rows.Err() != nil && isNotNonExistentMigrationsTable(rows.Err()) {
log.Panicln(rows.Err())
}
migrationsMap := make(MigrationsMap) migrationsMap := make(MigrationsMap)
for rows.Next() { for rows.Next() {
@ -90,10 +90,14 @@ func getDbMigrations() MigrationsMap {
migrationsMap[m.ID] = m.Name migrationsMap[m.ID] = m.Name
} }
if err := rows.Err(); err != nil {
log.Panicln(err)
}
return migrationsMap return migrationsMap
} }
func isNotNonExistentMigrationsTable(err error) bool { func isUndefinedTable(err error) bool {
return err.Error() != `ERROR: relation "migrations" does not exist (SQLSTATE 42P01)` var pgErr *pgconn.PgError
return errors.As(err, &pgErr) && pgErr.Code == "42P01"
} }

28
db/db_test.go Normal file
View File

@ -0,0 +1,28 @@
package db
import (
"errors"
"testing"
"github.com/jackc/pgx/v5/pgconn"
)
func TestIsUndefinedTable(t *testing.T) {
tests := []struct {
name string
err error
want bool
}{
{name: "undefined table", err: &pgconn.PgError{Code: "42P01"}, want: true},
{name: "other postgres error", err: &pgconn.PgError{Code: "23505"}},
{name: "ordinary error", err: errors.New("boom")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := isUndefinedTable(tt.err); got != tt.want {
t.Fatalf("isUndefinedTable() = %v, want %v", got, tt.want)
}
})
}
}

17
go.mod
View File

@ -9,8 +9,7 @@ require (
github.com/disintegration/imaging v1.6.2 github.com/disintegration/imaging v1.6.2
github.com/gofiber/fiber/v2 v2.52.14 github.com/gofiber/fiber/v2 v2.52.14
github.com/gofiber/template/html/v2 v2.1.3 github.com/gofiber/template/html/v2 v2.1.3
github.com/jackc/pgtype v1.10.0 github.com/jackc/pgx/v5 v5.10.0
github.com/jackc/pgx/v4 v4.15.0
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
) )
@ -19,27 +18,21 @@ require (
github.com/gofiber/template v1.8.3 // indirect github.com/gofiber/template v1.8.3 // indirect
github.com/gofiber/utils v1.1.0 // indirect github.com/gofiber/utils v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.11.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
github.com/jackc/pgproto3/v2 v2.2.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jackc/puddle v1.2.1 // indirect
github.com/klauspost/compress v1.17.9 // indirect github.com/klauspost/compress v1.17.9 // indirect
github.com/lib/pq v1.10.5 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.2.0 // indirect github.com/rivo/uniseg v0.2.0 // indirect
github.com/tinylib/msgp v1.2.5 // indirect github.com/tinylib/msgp v1.2.5 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/crypto v0.14.0 // indirect
golang.org/x/image v0.0.0-20220321031419-a8550c1d254a // indirect golang.org/x/image v0.0.0-20220321031419-a8550c1d254a // indirect
golang.org/x/sync v0.17.0 // indirect
golang.org/x/sys v0.28.0 // indirect golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/text v0.29.0 // indirect
) )

177
go.sum
View File

@ -1,12 +1,5 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
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/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
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=
@ -14,9 +7,6 @@ github.com/dchest/captcha v1.1.0 h1:2kt47EoYUUkaISobUdTbqwx55xvKOJxyScVfw25xzhQ=
github.com/dchest/captcha v1.1.0/go.mod h1:7zoElIawLp7GUMLcj54K9kbw+jEyvz2K0FDdRRYhvWo= github.com/dchest/captcha v1.1.0/go.mod h1:7zoElIawLp7GUMLcj54K9kbw+jEyvz2K0FDdRRYhvWo=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofiber/fiber/v2 v2.52.14 h1:Of3L+9qVFaQNwPlcmEdl5IIodHz8BSE0j37R7rWu4pE= github.com/gofiber/fiber/v2 v2.52.14 h1:Of3L+9qVFaQNwPlcmEdl5IIodHz8BSE0j37R7rWu4pE=
github.com/gofiber/fiber/v2 v2.52.14/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw= github.com/gofiber/fiber/v2 v2.52.14/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc= github.com/gofiber/template v1.8.3 h1:hzHdvMwMo/T2kouz2pPCA0zGiLCeMnoGsQZBTSYgZxc=
@ -25,83 +15,22 @@ github.com/gofiber/template/html/v2 v2.1.3 h1:n1LYBtmr9C0V/k/3qBblXyMxV5B0o/gpb6
github.com/gofiber/template/html/v2 v2.1.3/go.mod h1:U5Fxgc5KpyujU9OqKzy6Kn6Qup6Tm7zdsISR+VpnHRE= github.com/gofiber/template/html/v2 v2.1.3/go.mod h1:U5Fxgc5KpyujU9OqKzy6Kn6Qup6Tm7zdsISR+VpnHRE=
github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM= github.com/gofiber/utils v1.1.0 h1:vdEBpn7AzIUJRhe+CiTOJdUcTg4Q9RK+pEa0KPbLdrM=
github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0= github.com/gofiber/utils v1.1.0/go.mod h1:poZpsnhBykfnY1Mc0KeEa6mSHrS3dV0+oBWyeQmb2e0=
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/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
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.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.11.0 h1:HiHArx4yFbwl91X3qqIHtUFoiIfLNJXCQRsnzkiwwaQ=
github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
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/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns=
github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA=
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/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-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.10.0 h1:ILnBWrRMSXGczYvmkYD6PsYyVFUNLTnIUJHHDLmqk38=
github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
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-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.15.0 h1:B7dTkXsdILD3MF987WGGCcg+tvLW6bZJdEcqVFeU//w=
github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw=
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 v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.2.1 h1:gI8os0wpRXFd4FiAY2dWiqRK037tjj3t7rKFeO4X5iw=
github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
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.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ=
github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
@ -109,33 +38,15 @@ github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6T
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY=
github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
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/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
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/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po= github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po=
github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@ -144,85 +55,21 @@ github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1S
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= 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/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
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-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.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20220321031419-a8550c1d254a h1:LnH9RNcpPv5Kzi15lXg42lYMPUf0x8CuPv1YnvBWZAg= golang.org/x/image v0.0.0-20220321031419-a8550c1d254a h1:LnH9RNcpPv5Kzi15lXg42lYMPUf0x8CuPv1YnvBWZAg=
golang.org/x/image v0.0.0-20220321031419-a8550c1d254a/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/image v0.0.0-20220321031419-a8550c1d254a/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

View File

@ -1,6 +1,7 @@
package main package main
import ( import (
"context"
"log" "log"
"strconv" "strconv"
"strings" "strings"
@ -24,13 +25,14 @@ import (
) )
func main() { func main() {
ctx := context.Background()
config.Init() config.Init()
db.Init() db.Init(ctx)
db.Migrate() db.Migrate(ctx)
defer db.Pool.Close() defer db.Pool.Close()
if config.App.IsDbSeeded { if config.App.IsDbSeeded {
repositories.Seed() repositories.Seed(ctx)
} }
if config.App.Env == "production" { if config.App.Env == "production" {

View File

@ -4,22 +4,33 @@ import (
"context" "context"
Db "micrach/db" Db "micrach/db"
"github.com/jackc/pgtype" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v4"
) )
type FilesRepository struct{} type FilesRepository struct{}
var Files FilesRepository var Files FilesRepository
func (r *FilesRepository) Create(f File) error { func toInt32s(values []int) []int32 {
if values == nil {
return nil
}
result := make([]int32, len(values))
for i, value := range values {
result[i] = int32(value)
}
return result
}
func (r *FilesRepository) Create(ctx context.Context, f File) error {
sql := ` sql := `
INSERT INTO files (post_id, name, ext, size) INSERT INTO files (post_id, name, ext, size)
VALUES ($1, $2, $3, $4) VALUES ($1, $2, $3, $4)
` `
row := Db.Pool.QueryRow( row := Db.Pool.QueryRow(
context.TODO(), sql, f.PostID, f.Name, f.Ext, f.Size, ctx, sql, f.PostID, f.Name, f.Ext, f.Size,
) )
err := row.Scan() err := row.Scan()
@ -30,7 +41,7 @@ func (r *FilesRepository) Create(f File) error {
return nil return nil
} }
func (r *FilesRepository) GetByPostIDs(postIDs []int) (map[int][]File, error) { func (r *FilesRepository) GetByPostIDs(ctx context.Context, postIDs []int) (map[int][]File, error) {
sql := ` sql := `
SELECT id, post_id, name, size, ext SELECT id, post_id, name, size, ext
FROM files FROM files
@ -38,16 +49,11 @@ func (r *FilesRepository) GetByPostIDs(postIDs []int) (map[int][]File, error) {
ORDER BY id ASC ORDER BY id ASC
` `
ids := &pgtype.Int4Array{} rows, err := Db.Pool.Query(ctx, sql, toInt32s(postIDs))
ids.Set(postIDs)
rows, err := Db.Pool.Query(context.TODO(), sql, ids)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if rows.Err() != nil { defer rows.Close()
return nil, rows.Err()
}
filesMapByPostId := make(map[int][]File) filesMapByPostId := make(map[int][]File)
for rows.Next() { for rows.Next() {
@ -66,11 +72,14 @@ func (r *FilesRepository) GetByPostIDs(postIDs []int) (map[int][]File, error) {
file, file,
) )
} }
if err := rows.Err(); err != nil {
return nil, err
}
return filesMapByPostId, nil return filesMapByPostId, nil
} }
func (r *FilesRepository) CreateInTx(tx pgx.Tx, f File) (int, error) { func (r *FilesRepository) CreateInTx(ctx context.Context, tx pgx.Tx, f File) (int, error) {
sql := ` sql := `
INSERT INTO files (post_id, name, ext, size) INSERT INTO files (post_id, name, ext, size)
VALUES ($1, $2, $3, $4) VALUES ($1, $2, $3, $4)
@ -78,7 +87,7 @@ func (r *FilesRepository) CreateInTx(tx pgx.Tx, f File) (int, error) {
` `
row := tx.QueryRow( row := tx.QueryRow(
context.TODO(), sql, f.PostID, f.Name, f.Ext, f.Size, ctx, sql, f.PostID, f.Name, f.Ext, f.Size,
) )
createdFile := new(File) createdFile := new(File)

View File

@ -0,0 +1,26 @@
package repositories
import (
"reflect"
"testing"
)
func TestToInt32s(t *testing.T) {
tests := []struct {
name string
in []int
want []int32
}{
{name: "nil", in: nil, want: nil},
{name: "empty", in: []int{}, want: []int32{}},
{name: "values", in: []int{1, 2, 2147483647}, want: []int32{1, 2, 2147483647}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := toInt32s(tt.in); !reflect.DeepEqual(got, tt.want) {
t.Fatalf("toInt32s() = %#v, want %#v", got, tt.want)
}
})
}
}

View File

@ -6,14 +6,14 @@ import (
Db "micrach/db" Db "micrach/db"
"time" "time"
"github.com/jackc/pgx/v4" "github.com/jackc/pgx/v5"
) )
type PostsRepository struct{} type PostsRepository struct{}
var Posts PostsRepository var Posts PostsRepository
func (r *PostsRepository) Get(limit, offset int) ([]Post, error) { func (r *PostsRepository) Get(ctx context.Context, limit, offset int) ([]Post, error) {
sql := ` sql := `
SELECT id, title, text, created_at SELECT id, title, text, created_at
FROM posts FROM posts
@ -25,13 +25,11 @@ func (r *PostsRepository) Get(limit, offset int) ([]Post, error) {
LIMIT $2 LIMIT $2
` `
rows, err := Db.Pool.Query(context.TODO(), sql, offset, limit) rows, err := Db.Pool.Query(ctx, sql, offset, limit)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if rows.Err() != nil { defer rows.Close()
return nil, err
}
postsMap := make(map[int]Post) postsMap := make(map[int]Post)
var postIDs []int var postIDs []int
@ -45,8 +43,11 @@ func (r *PostsRepository) Get(limit, offset int) ([]Post, error) {
postsMap[post.ID] = post postsMap[post.ID] = post
postIDs = append(postIDs, post.ID) postIDs = append(postIDs, post.ID)
} }
if err := rows.Err(); err != nil {
return nil, err
}
filesMap, err := Files.GetByPostIDs(postIDs) filesMap, err := Files.GetByPostIDs(ctx, postIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -61,7 +62,7 @@ func (r *PostsRepository) Get(limit, offset int) ([]Post, error) {
return posts, nil return posts, nil
} }
func (r *PostsRepository) GetThreadsCount() (int, error) { func (r *PostsRepository) GetThreadsCount(ctx context.Context) (int, error) {
sql := ` sql := `
SELECT COUNT(*) SELECT COUNT(*)
FROM posts FROM posts
@ -71,7 +72,7 @@ func (r *PostsRepository) GetThreadsCount() (int, error) {
AND is_archived != true AND is_archived != true
` `
row := Db.Pool.QueryRow(context.TODO(), sql) row := Db.Pool.QueryRow(ctx, sql)
var count int var count int
err := row.Scan(&count) err := row.Scan(&count)
if err != nil { if err != nil {
@ -80,7 +81,7 @@ func (r *PostsRepository) GetThreadsCount() (int, error) {
return count, nil return count, nil
} }
func (r *PostsRepository) Create(p Post) (int, error) { func (r *PostsRepository) Create(ctx context.Context, p Post) (int, error) {
sql := ` sql := `
INSERT INTO posts (is_parent, parent_id, title, text, is_sage, updated_at) INSERT INTO posts (is_parent, parent_id, title, text, is_sage, updated_at)
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5, $6)
@ -90,11 +91,11 @@ func (r *PostsRepository) Create(p Post) (int, error) {
var row pgx.Row var row pgx.Row
if p.IsParent { if p.IsParent {
row = Db.Pool.QueryRow( row = Db.Pool.QueryRow(
context.TODO(), sql, p.IsParent, nil, p.Title, p.Text, p.IsSage, time.Now(), ctx, sql, p.IsParent, nil, p.Title, p.Text, p.IsSage, time.Now(),
) )
} else { } else {
row = Db.Pool.QueryRow( row = Db.Pool.QueryRow(
context.TODO(), sql, p.IsParent, p.ParentID, p.Title, p.Text, p.IsSage, nil, ctx, sql, p.IsParent, p.ParentID, p.Title, p.Text, p.IsSage, nil,
) )
} }
@ -107,7 +108,7 @@ func (r *PostsRepository) Create(p Post) (int, error) {
return createdPost.ID, nil return createdPost.ID, nil
} }
func (r *PostsRepository) GetThreadByPostID(ID int) ([]Post, error) { func (r *PostsRepository) GetThreadByPostID(ctx context.Context, ID int) ([]Post, error) {
sql := ` sql := `
SELECT SELECT
id, id,
@ -124,13 +125,11 @@ func (r *PostsRepository) GetThreadByPostID(ID int) ([]Post, error) {
ORDER BY created_at ASC ORDER BY created_at ASC
` `
rows, err := Db.Pool.Query(context.TODO(), sql, ID) rows, err := Db.Pool.Query(ctx, sql, ID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if rows.Err() != nil { defer rows.Close()
return nil, err
}
postsMap := make(map[int]Post) postsMap := make(map[int]Post)
var postIDs []int var postIDs []int
@ -152,8 +151,11 @@ func (r *PostsRepository) GetThreadByPostID(ID int) ([]Post, error) {
postsMap[post.ID] = post postsMap[post.ID] = post
postIDs = append(postIDs, post.ID) postIDs = append(postIDs, post.ID)
} }
if err := rows.Err(); err != nil {
return nil, err
}
filesMap, err := Files.GetByPostIDs(postIDs) filesMap, err := Files.GetByPostIDs(ctx, postIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -169,7 +171,7 @@ func (r *PostsRepository) GetThreadByPostID(ID int) ([]Post, error) {
} }
// Check if thread is archived // Check if thread is archived
func (r *PostsRepository) GetIfThreadIsArchived(ID int) (bool, error) { func (r *PostsRepository) GetIfThreadIsArchived(ctx context.Context, ID int) (bool, error) {
sql := ` sql := `
SELECT is_archived SELECT is_archived
FROM posts FROM posts
@ -177,7 +179,7 @@ func (r *PostsRepository) GetIfThreadIsArchived(ID int) (bool, error) {
LIMIT 1 LIMIT 1
` `
row := Db.Pool.QueryRow(context.TODO(), sql, ID) row := Db.Pool.QueryRow(ctx, sql, ID)
var isArchived bool var isArchived bool
err := row.Scan(&isArchived) err := row.Scan(&isArchived)
if err != nil { if err != nil {
@ -186,7 +188,7 @@ func (r *PostsRepository) GetIfThreadIsArchived(ID int) (bool, error) {
return isArchived, nil return isArchived, nil
} }
func (r *PostsRepository) CreateInTx(tx pgx.Tx, p Post) (int, error) { func (r *PostsRepository) CreateInTx(ctx context.Context, tx pgx.Tx, p Post) (int, error) {
sql := ` sql := `
INSERT INTO posts (is_parent, parent_id, title, text, is_sage, updated_at) INSERT INTO posts (is_parent, parent_id, title, text, is_sage, updated_at)
VALUES ($1, $2, $3, $4, $5, $6) VALUES ($1, $2, $3, $4, $5, $6)
@ -196,11 +198,11 @@ func (r *PostsRepository) CreateInTx(tx pgx.Tx, p Post) (int, error) {
var row pgx.Row var row pgx.Row
if p.IsParent { if p.IsParent {
row = tx.QueryRow( row = tx.QueryRow(
context.TODO(), sql, p.IsParent, nil, p.Title, p.Text, p.IsSage, time.Now(), ctx, sql, p.IsParent, nil, p.Title, p.Text, p.IsSage, time.Now(),
) )
} else { } else {
row = tx.QueryRow( row = tx.QueryRow(
context.TODO(), sql, p.IsParent, p.ParentID, p.Title, p.Text, p.IsSage, nil, ctx, sql, p.IsParent, p.ParentID, p.Title, p.Text, p.IsSage, nil,
) )
} }
@ -213,7 +215,7 @@ func (r *PostsRepository) CreateInTx(tx pgx.Tx, p Post) (int, error) {
return createdPost.ID, nil return createdPost.ID, nil
} }
func (r *PostsRepository) GetOldestThreadUpdatedAt() (time.Time, error) { func (r *PostsRepository) GetOldestThreadUpdatedAt(ctx context.Context) (time.Time, error) {
sql := ` sql := `
SELECT updated_at SELECT updated_at
FROM posts FROM posts
@ -226,7 +228,7 @@ func (r *PostsRepository) GetOldestThreadUpdatedAt() (time.Time, error) {
LIMIT 1 LIMIT 1
` `
row := Db.Pool.QueryRow(context.TODO(), sql, Config.App.ThreadsMaxCount) row := Db.Pool.QueryRow(ctx, sql, Config.App.ThreadsMaxCount)
var updatedAt time.Time var updatedAt time.Time
err := row.Scan(&updatedAt) err := row.Scan(&updatedAt)
if err != nil { if err != nil {
@ -235,7 +237,7 @@ func (r *PostsRepository) GetOldestThreadUpdatedAt() (time.Time, error) {
return updatedAt, nil return updatedAt, nil
} }
func (r *PostsRepository) ArchiveThreadsFrom(t time.Time) error { func (r *PostsRepository) ArchiveThreadsFrom(ctx context.Context, t time.Time) error {
sql := ` sql := `
UPDATE posts UPDATE posts
SET is_archived = true SET is_archived = true
@ -245,12 +247,12 @@ func (r *PostsRepository) ArchiveThreadsFrom(t time.Time) error {
AND updated_at <= $1 AND updated_at <= $1
` `
_, err := Db.Pool.Exec(context.TODO(), sql, t) _, err := Db.Pool.Exec(ctx, sql, t)
return err return err
} }
// Returns count of posts in thread by thread ID // Returns count of posts in thread by thread ID
func (r *PostsRepository) GetThreadPostsCount(id int) (int, error) { func (r *PostsRepository) GetThreadPostsCount(ctx context.Context, id int) (int, error) {
sql := ` sql := `
SELECT COUNT(*) SELECT COUNT(*)
FROM posts FROM posts
@ -259,7 +261,7 @@ func (r *PostsRepository) GetThreadPostsCount(id int) (int, error) {
OR (parent_id = $1 AND is_deleted != true) OR (parent_id = $1 AND is_deleted != true)
` `
row := Db.Pool.QueryRow(context.TODO(), sql, id) row := Db.Pool.QueryRow(ctx, sql, id)
var count int var count int
err := row.Scan(&count) err := row.Scan(&count)
if err != nil { if err != nil {
@ -269,13 +271,13 @@ func (r *PostsRepository) GetThreadPostsCount(id int) (int, error) {
} }
// Updates threads updated at time by thread ID // Updates threads updated at time by thread ID
func (r *PostsRepository) BumpThreadInTx(tx pgx.Tx, id int) error { func (r *PostsRepository) BumpThreadInTx(ctx context.Context, tx pgx.Tx, id int) error {
sql := ` sql := `
UPDATE posts UPDATE posts
SET updated_at = now() SET updated_at = now()
WHERE id = $1 WHERE id = $1
` `
_, err := tx.Exec(context.TODO(), sql, id) _, err := tx.Exec(ctx, sql, id)
return err return err
} }

View File

@ -1,6 +1,7 @@
package repositories package repositories
import ( import (
"context"
"log" "log"
"math/rand" "math/rand"
"time" "time"
@ -63,7 +64,7 @@ func getPost(id int, pid *int) Post {
} }
} }
func seedDb() { func seedDb(ctx context.Context) {
// preparing seed data with parent posts with files // preparing seed data with parent posts with files
var parentPosts []Post var parentPosts []Post
for i := 1; i < 10; i++ { for i := 1; i < 10; i++ {
@ -73,7 +74,7 @@ func seedDb() {
for _, parentPost := range parentPosts { for _, parentPost := range parentPosts {
// saving parent post in db // saving parent post in db
parentPostID, err := Posts.Create(parentPost) parentPostID, err := Posts.Create(ctx, parentPost)
if err != nil { if err != nil {
log.Panicln(err) log.Panicln(err)
} }
@ -81,7 +82,7 @@ func seedDb() {
// saving parent post files // saving parent post files
for _, file := range parentPost.Files { for _, file := range parentPost.Files {
file.PostID = parentPostID file.PostID = parentPostID
err = Files.Create(file) err = Files.Create(ctx, file)
if err != nil { if err != nil {
log.Panicln(err) log.Panicln(err)
} }
@ -91,7 +92,7 @@ func seedDb() {
for i := 0; i < 10; i++ { for i := 0; i < 10; i++ {
// getting child post with files // getting child post with files
childPost := getPost(0, &parentPostID) childPost := getPost(0, &parentPostID)
childPostID, err := Posts.Create(childPost) childPostID, err := Posts.Create(ctx, childPost)
if err != nil { if err != nil {
log.Panicln(err) log.Panicln(err)
} }
@ -99,7 +100,7 @@ func seedDb() {
// saving child post files // saving child post files
for _, file := range childPost.Files { for _, file := range childPost.Files {
file.PostID = childPostID file.PostID = childPostID
err = Files.Create(file) err = Files.Create(ctx, file)
if err != nil { if err != nil {
log.Panicln(err) log.Panicln(err)
} }
@ -109,7 +110,7 @@ func seedDb() {
} }
} }
func Seed() { func Seed(ctx context.Context) {
seedDb() seedDb(ctx)
log.Println("seeds - online") log.Println("seeds - online")
} }