Compare commits

..

3 Commits

Author SHA1 Message Date
761babfa1a refactor(backend/models): use pgtype for nullable fields 2025-07-03 16:16:44 +03:00
59eacd6bc5 refactor(backend/db): remove ctx argument from transaction wrapper 2025-07-03 15:35:56 +03:00
5ac528be05 feat(backend/db): add util functions
`sortToSQL` and unimplemented `filterToSQL`
2025-07-03 14:55:30 +03:00
3 changed files with 178 additions and 120 deletions

View File

@ -30,13 +30,14 @@ func InitDB(connString string) error {
return nil return nil
} }
func transaction(ctx context.Context, handler func(pgx.Tx) (statusCode int, err error)) (statusCode int, err error) { func transaction(handler func(context.Context, pgx.Tx) (statusCode int, err error)) (statusCode int, err error) {
ctx := context.Background()
tx, err := connPool.Begin(ctx) tx, err := connPool.Begin(ctx)
if err != nil { if err != nil {
statusCode = http.StatusInternalServerError statusCode = http.StatusInternalServerError
return return
} }
statusCode, err = handler(tx) statusCode, err = handler(ctx, tx)
if err != nil { if err != nil {
tx.Rollback(ctx) tx.Rollback(ctx)
return return

53
backend/db/utils.go Normal file
View File

@ -0,0 +1,53 @@
package db
import (
"fmt"
"net/http"
"strconv"
"strings"
)
// Convert "filter" URL param to SQL "WHERE" condition
func filterToSQL(filter string) (sql string, statusCode int, err error) {
// filterTokens := strings.Split(string(filter), ";")
sql = "(true)"
return
}
// Convert "sort" URL param to SQL "ORDER BY"
func sortToSQL(sort string) (sql string, statusCode int, err error) {
if sort == "" {
return
}
sortOptions := strings.Split(sort, ",")
sql = " ORDER BY "
for i, sortOption := range sortOptions {
sortOrder := sortOption[:1]
sortColumn := sortOption[1:]
// parse sorting order marker
switch sortOrder {
case "+":
sortOrder = "ASC"
case "-":
sortOrder = "DESC"
default:
err = fmt.Errorf("invalid sorting order mark: %q", sortOrder)
statusCode = http.StatusBadRequest
return
}
// validate sorting column
var n int
n, err = strconv.Atoi(sortColumn)
if err != nil || n < 0 {
err = fmt.Errorf("invalid sorting column: %q", sortColumn)
statusCode = http.StatusBadRequest
return
}
// add sorting option to query
if i > 0 {
sql += ","
}
sql += fmt.Sprintf("%s %s NULLS LAST", sortColumn, sortOrder)
}
return
}

View File

@ -1,6 +1,10 @@
package models package models
import "time" import (
"time"
"github.com/jackc/pgx/v5/pgtype"
)
type User struct { type User struct {
Name string `json:"name"` Name string `json:"name"`
@ -13,28 +17,28 @@ type MIME struct {
} }
type Category struct { type Category struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Color string `json:"color"` Color pgtype.Text `json:"color"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
Creator User `json:"creator"` Creator User `json:"creator"`
} }
type File struct { type File struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name pgtype.Text `json:"name"`
MIME MIME `json:"mime"` MIME MIME `json:"mime"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
Creator User `json:"creator"` Creator User `json:"creator"`
} }
type Tag struct { type Tag struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Color string `json:"color"` Color pgtype.Text `json:"color"`
Category Category `json:"category"` Category Category `json:"category"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
Creator User `json:"creator"` Creator User `json:"creator"`
} }
type Autotag struct { type Autotag struct {