Ctrl K
ComunidadGuías…

internal/store/postgres/item_prices.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package postgres

import (
	"context"
	"errors"
	"fmt"

	"github.com/jackc/pgx/v5"

	"telesrv/internal/domain"
	"telesrv/internal/store/postgres/sqlcgen"
)

// ItemPriceStore persists the admin-configurable shop prices: per-product
// overrides in item_prices (migration 20260926010000) and shop-wide settings
// in shop_settings (migration 20260927010000). It stores overrides only —
// catalog defaults live in domain.DefaultItemPrices, and merging happens above
// the store so a default change never needs a data migration.
type ItemPriceStore struct {
	db sqlcgen.DBTX
}

// NewItemPriceStore builds the store on a pgx pool or transaction.
func NewItemPriceStore(db sqlcgen.DBTX) *ItemPriceStore {
	return &ItemPriceStore{db: db}
}

// ListItemPrices returns stored overrides, optionally only enabled ones.
func (s *ItemPriceStore) ListItemPrices(ctx context.Context, enabledOnly bool) ([]domain.ItemPrice, error) {
	query := `SELECT product_code, stars_price, bid, enabled, updated_by,
extract(epoch FROM updated_at)::bigint AS updated_at
FROM item_prices`
	if enabledOnly {
		query += ` WHERE enabled`
	}
	query += ` ORDER BY product_code`
	rows, err := s.db.Query(ctx, query)
	if err != nil {
		return nil, fmt.Errorf("list item prices: %w", err)
	}
	defer rows.Close()
	out := make([]domain.ItemPrice, 0)
	for rows.Next() {
		var p domain.ItemPrice
		if err := rows.Scan(&p.ProductCode, &p.StarsPrice, &p.Bid, &p.Enabled,
			&p.UpdatedBy, &p.UpdatedAt); err != nil {
			return nil, fmt.Errorf("scan item price: %w", err)
		}
		out = append(out, p)
	}
	return out, rows.Err()
}

// UpsertItemPrice stores one product's override, replacing any previous row.
func (s *ItemPriceStore) UpsertItemPrice(ctx context.Context, p domain.ItemPrice) (domain.ItemPrice, error) {
	row := s.db.QueryRow(ctx, `INSERT INTO item_prices (product_code, stars_price, bid, enabled, updated_by)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT (product_code) DO UPDATE SET
stars_price = EXCLUDED.stars_price,
bid = EXCLUDED.bid,
enabled = EXCLUDED.enabled,
updated_by = EXCLUDED.updated_by,
updated_at = now()
RETURNING product_code, stars_price, bid, enabled, updated_by,
extract(epoch FROM updated_at)::bigint AS updated_at`,
		p.ProductCode, p.StarsPrice, p.Bid, p.Enabled, p.UpdatedBy)
	var out domain.ItemPrice
	if err := row.Scan(&out.ProductCode, &out.StarsPrice, &out.Bid, &out.Enabled,
		&out.UpdatedBy, &out.UpdatedAt); err != nil {
		return domain.ItemPrice{}, fmt.Errorf("upsert item price: %w", err)
	}
	return out, nil
}

// GetShopSetting reads one shop-wide setting. found=false means the key was
// never written; callers then fall back to their own default rather than
// inventing a stored value.
func (s *ItemPriceStore) GetShopSetting(ctx context.Context, key string) (value int64, found bool, err error) {
	if err := s.db.QueryRow(ctx,
		`SELECT value FROM shop_settings WHERE key = $1`, key,
	).Scan(&value); err != nil {
		if errors.Is(err, pgx.ErrNoRows) {
			return 0, false, nil
		}
		return 0, false, fmt.Errorf("read shop setting %q: %w", key, err)
	}
	return value, true, nil
}

// SetShopSetting writes one shop-wide setting.
func (s *ItemPriceStore) SetShopSetting(ctx context.Context, key string, value int64, updatedBy string) error {
	_, err := s.db.Exec(ctx, `INSERT INTO shop_settings (key, value, updated_by)
VALUES ($1, $2, $3)
ON CONFLICT (key) DO UPDATE SET
value = EXCLUDED.value,
updated_by = EXCLUDED.updated_by,
updated_at = now()`, key, value, updatedBy)
	if err != nil {
		return fmt.Errorf("write shop setting %q: %w", key, err)
	}
	return nil
}