feat(admin): sortable product list column headers
Click a column header (名称/品牌/条码/品类/状态/质量分) to sort asc, click again for desc, and a third time to clear back to the default most-recently-updated order. Sort key/direction are whitelisted server-side. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -9,6 +9,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
@@ -50,8 +51,34 @@ type ProductRow struct {
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// productSortColumns whitelists the sortable list columns, mapping the API sort
|
||||
// key to a SQL expression. NULLs sort last regardless of direction.
|
||||
var productSortColumns = map[string]string{
|
||||
"name": "p.name",
|
||||
"brand": "b.name",
|
||||
"gtin": "p.gtin",
|
||||
"category_path": "c.path",
|
||||
"status": "p.status",
|
||||
"quality_score": "p.quality_score",
|
||||
"updated_at": "p.updated_at",
|
||||
}
|
||||
|
||||
// productOrderBy returns a safe ORDER BY clause for the given sort key/direction,
|
||||
// falling back to the default (most recently updated first) for unknown keys.
|
||||
func productOrderBy(sort, order string) string {
|
||||
col, ok := productSortColumns[sort]
|
||||
if !ok {
|
||||
return "p.updated_at DESC"
|
||||
}
|
||||
dir := "ASC"
|
||||
if strings.EqualFold(order, "desc") {
|
||||
dir = "DESC"
|
||||
}
|
||||
return col + " " + dir + " NULLS LAST, p.updated_at DESC"
|
||||
}
|
||||
|
||||
// ListProducts returns a paginated, optionally name/gtin-filtered list.
|
||||
func (s *Store) ListProducts(ctx context.Context, q string, limit, offset int) ([]ProductRow, int, error) {
|
||||
func (s *Store) ListProducts(ctx context.Context, q, sort, order string, limit, offset int) ([]ProductRow, int, error) {
|
||||
args := []any{}
|
||||
where := "WHERE 1=1"
|
||||
if q != "" {
|
||||
@@ -84,7 +111,8 @@ FROM product p
|
||||
LEFT JOIN brand b ON b.id = p.brand_id
|
||||
LEFT JOIN category c ON c.id = p.category_id
|
||||
LEFT JOIN food_detail f ON f.product_id = p.id ` + where +
|
||||
" ORDER BY p.updated_at DESC LIMIT $" + strconv.Itoa(len(args)-1) + " OFFSET $" + strconv.Itoa(len(args))
|
||||
" ORDER BY " + productOrderBy(sort, order) +
|
||||
" LIMIT $" + strconv.Itoa(len(args)-1) + " OFFSET $" + strconv.Itoa(len(args))
|
||||
|
||||
rows, err := s.pool.Query(ctx, sql, args...)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user