Add product features, Heleket crypto payments and order status icons.
This commit is contained in:
@@ -9,6 +9,13 @@ import (
|
||||
"shop/internal/models"
|
||||
)
|
||||
|
||||
const orderSelect = `
|
||||
SELECT id, user_id, guest_name, guest_email, guest_phone, address, total, status,
|
||||
COALESCE(payment_method, 'cod'), COALESCE(heleket_uuid, ''), COALESCE(heleket_order_id, ''),
|
||||
COALESCE(heleket_payment_url, ''), COALESCE(heleket_payment_status, ''), created_at
|
||||
FROM orders
|
||||
`
|
||||
|
||||
type OrderRepository struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
@@ -17,6 +24,15 @@ func NewOrderRepository(pool *pgxpool.Pool) *OrderRepository {
|
||||
return &OrderRepository{pool: pool}
|
||||
}
|
||||
|
||||
func scanOrder(row pgx.Row) (models.Order, error) {
|
||||
var o models.Order
|
||||
err := row.Scan(
|
||||
&o.ID, &o.UserID, &o.GuestName, &o.GuestEmail, &o.GuestPhone, &o.Address, &o.Total, &o.Status,
|
||||
&o.PaymentMethod, &o.HeleketUUID, &o.HeleketOrderID, &o.HeleketPaymentURL, &o.HeleketPaymentStatus, &o.CreatedAt,
|
||||
)
|
||||
return o, err
|
||||
}
|
||||
|
||||
func (r *OrderRepository) Create(ctx context.Context, order *models.Order, items []models.CartItem) error {
|
||||
tx, err := r.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
@@ -24,11 +40,20 @@ func (r *OrderRepository) Create(ctx context.Context, order *models.Order, items
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
status := order.Status
|
||||
if status == "" {
|
||||
status = "pending"
|
||||
}
|
||||
paymentMethod := order.PaymentMethod
|
||||
if paymentMethod == "" {
|
||||
paymentMethod = "cod"
|
||||
}
|
||||
|
||||
err = tx.QueryRow(ctx, `
|
||||
INSERT INTO orders (user_id, guest_name, guest_email, guest_phone, address, total, status)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, 'pending')
|
||||
INSERT INTO orders (user_id, guest_name, guest_email, guest_phone, address, total, status, payment_method)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id, created_at
|
||||
`, order.UserID, order.GuestName, order.GuestEmail, order.GuestPhone, order.Address, order.Total,
|
||||
`, order.UserID, order.GuestName, order.GuestEmail, order.GuestPhone, order.Address, order.Total, status, paymentMethod,
|
||||
).Scan(&order.ID, &order.CreatedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -47,11 +72,37 @@ func (r *OrderRepository) Create(ctx context.Context, order *models.Order, items
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
func (r *OrderRepository) UpdateHeleketPayment(ctx context.Context, orderID int, uuid, externalID, paymentURL, paymentStatus string) error {
|
||||
_, err := r.pool.Exec(ctx, `
|
||||
UPDATE orders SET
|
||||
heleket_uuid = $1,
|
||||
heleket_order_id = $2,
|
||||
heleket_payment_url = $3,
|
||||
heleket_payment_status = $4
|
||||
WHERE id = $5
|
||||
`, uuid, externalID, paymentURL, paymentStatus, orderID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *OrderRepository) UpdateHeleketStatus(ctx context.Context, orderID int, paymentStatus, orderStatus string) error {
|
||||
_, err := r.pool.Exec(ctx, `
|
||||
UPDATE orders SET heleket_payment_status = $1, status = $2 WHERE id = $3
|
||||
`, paymentStatus, orderStatus, orderID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *OrderRepository) GetByHeleketOrderID(ctx context.Context, externalID string) (models.Order, error) {
|
||||
row := r.pool.QueryRow(ctx, orderSelect+` WHERE heleket_order_id = $1`, externalID)
|
||||
o, err := scanOrder(row)
|
||||
if err != nil {
|
||||
return models.Order{}, err
|
||||
}
|
||||
o.Items, err = r.items(ctx, o.ID)
|
||||
return o, err
|
||||
}
|
||||
|
||||
func (r *OrderRepository) ByUser(ctx context.Context, userID int) ([]models.Order, error) {
|
||||
rows, err := r.pool.Query(ctx, `
|
||||
SELECT id, user_id, guest_name, guest_email, guest_phone, address, total, status, created_at
|
||||
FROM orders WHERE user_id = $1 ORDER BY created_at DESC
|
||||
`, userID)
|
||||
rows, err := r.pool.Query(ctx, orderSelect+` WHERE user_id = $1 ORDER BY created_at DESC`, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -59,8 +110,8 @@ func (r *OrderRepository) ByUser(ctx context.Context, userID int) ([]models.Orde
|
||||
|
||||
var orders []models.Order
|
||||
for rows.Next() {
|
||||
var o models.Order
|
||||
if err := rows.Scan(&o.ID, &o.UserID, &o.GuestName, &o.GuestEmail, &o.GuestPhone, &o.Address, &o.Total, &o.Status, &o.CreatedAt); err != nil {
|
||||
o, err := scanOrder(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
o.Items, err = r.items(ctx, o.ID)
|
||||
@@ -94,10 +145,7 @@ func (r *OrderRepository) items(ctx context.Context, orderID int) ([]models.Orde
|
||||
}
|
||||
|
||||
func (r *OrderRepository) All(ctx context.Context, limit int) ([]models.Order, error) {
|
||||
rows, err := r.pool.Query(ctx, `
|
||||
SELECT id, user_id, guest_name, guest_email, guest_phone, address, total, status, created_at
|
||||
FROM orders ORDER BY created_at DESC LIMIT $1
|
||||
`, limit)
|
||||
rows, err := r.pool.Query(ctx, orderSelect+` ORDER BY created_at DESC LIMIT $1`, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -106,11 +154,8 @@ func (r *OrderRepository) All(ctx context.Context, limit int) ([]models.Order, e
|
||||
}
|
||||
|
||||
func (r *OrderRepository) GetByID(ctx context.Context, id int) (models.Order, error) {
|
||||
var o models.Order
|
||||
err := r.pool.QueryRow(ctx, `
|
||||
SELECT id, user_id, guest_name, guest_email, guest_phone, address, total, status, created_at
|
||||
FROM orders WHERE id = $1
|
||||
`, id).Scan(&o.ID, &o.UserID, &o.GuestName, &o.GuestEmail, &o.GuestPhone, &o.Address, &o.Total, &o.Status, &o.CreatedAt)
|
||||
row := r.pool.QueryRow(ctx, orderSelect+` WHERE id = $1`, id)
|
||||
o, err := scanOrder(row)
|
||||
if err != nil {
|
||||
return models.Order{}, err
|
||||
}
|
||||
@@ -138,11 +183,10 @@ func (r *OrderRepository) Count(ctx context.Context) (int, error) {
|
||||
func (r *OrderRepository) collectOrders(ctx context.Context, rows pgx.Rows) ([]models.Order, error) {
|
||||
var orders []models.Order
|
||||
for rows.Next() {
|
||||
var o models.Order
|
||||
if err := rows.Scan(&o.ID, &o.UserID, &o.GuestName, &o.GuestEmail, &o.GuestPhone, &o.Address, &o.Total, &o.Status, &o.CreatedAt); err != nil {
|
||||
o, err := scanOrder(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var err error
|
||||
o.Items, err = r.items(ctx, o.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"shop/internal/models"
|
||||
)
|
||||
|
||||
type PriceHistoryRepository struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
func NewPriceHistoryRepository(pool *pgxpool.Pool) *PriceHistoryRepository {
|
||||
return &PriceHistoryRepository{pool: pool}
|
||||
}
|
||||
|
||||
func (r *PriceHistoryRepository) Record(ctx context.Context, productID int, price, salePrice float64) error {
|
||||
_, err := r.pool.Exec(ctx, `
|
||||
INSERT INTO product_price_history (product_id, price, sale_price)
|
||||
VALUES ($1, $2, NULLIF($3, 0))
|
||||
`, productID, price, salePrice)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *PriceHistoryRepository) ByProduct(ctx context.Context, productID int, limit int) ([]models.PriceHistoryEntry, error) {
|
||||
rows, err := r.pool.Query(ctx, `
|
||||
SELECT id, product_id, price, COALESCE(sale_price, 0), recorded_at
|
||||
FROM product_price_history
|
||||
WHERE product_id = $1
|
||||
ORDER BY recorded_at DESC
|
||||
LIMIT $2
|
||||
`, productID, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var entries []models.PriceHistoryEntry
|
||||
for rows.Next() {
|
||||
var e models.PriceHistoryEntry
|
||||
if err := rows.Scan(&e.ID, &e.ProductID, &e.Price, &e.SalePrice, &e.RecordedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
entries = append(entries, e)
|
||||
}
|
||||
return entries, rows.Err()
|
||||
}
|
||||
@@ -22,7 +22,10 @@ func NewProductRepository(pool *pgxpool.Pool) *ProductRepository {
|
||||
const productSelect = `
|
||||
SELECT p.id, p.name, p.description, p.details, p.price, COALESCE(p.sale_price, 0),
|
||||
p.image_url, COALESCE(c.name, p.category, 'Разное'), p.category_id,
|
||||
COALESCE(c.slug, ''), p.is_active, p.created_at, p.updated_at
|
||||
COALESCE(c.slug, ''), p.is_active,
|
||||
COALESCE(p.attr_size, ''), COALESCE(p.attr_color, ''), COALESCE(p.attr_material, ''),
|
||||
COALESCE(p.attr_weight, ''), COALESCE(p.attr_brand, ''),
|
||||
p.created_at, p.updated_at
|
||||
FROM products p
|
||||
LEFT JOIN categories c ON c.id = p.category_id
|
||||
`
|
||||
@@ -32,7 +35,9 @@ func (r *ProductRepository) scanProduct(row pgx.Row) (models.Product, error) {
|
||||
var catID sql.NullInt64
|
||||
err := row.Scan(
|
||||
&p.ID, &p.Name, &p.Description, &p.Details, &p.Price, &p.SalePrice,
|
||||
&p.ImageURL, &p.Category, &catID, &p.CategorySlug, &p.IsActive, &p.CreatedAt, &p.UpdatedAt,
|
||||
&p.ImageURL, &p.Category, &catID, &p.CategorySlug, &p.IsActive,
|
||||
&p.AttrSize, &p.AttrColor, &p.AttrMaterial, &p.AttrWeight, &p.AttrBrand,
|
||||
&p.CreatedAt, &p.UpdatedAt,
|
||||
)
|
||||
if catID.Valid {
|
||||
id := int(catID.Int64)
|
||||
@@ -94,10 +99,12 @@ func (r *ProductRepository) GetActiveByID(ctx context.Context, id int) (models.P
|
||||
|
||||
func (r *ProductRepository) Create(ctx context.Context, p *models.Product) error {
|
||||
return r.pool.QueryRow(ctx, `
|
||||
INSERT INTO products (name, description, details, price, sale_price, image_url, category, category_id, is_active)
|
||||
VALUES ($1, $2, $3, $4, NULLIF($5, 0), $6, $7, $8, $9)
|
||||
INSERT INTO products (name, description, details, price, sale_price, image_url, category, category_id, is_active,
|
||||
attr_size, attr_color, attr_material, attr_weight, attr_brand)
|
||||
VALUES ($1, $2, $3, $4, NULLIF($5, 0), $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
||||
RETURNING id, created_at, updated_at
|
||||
`, p.Name, p.Description, p.Details, p.Price, p.SalePrice, p.ImageURL, p.Category, p.CategoryID, p.IsActive,
|
||||
p.AttrSize, p.AttrColor, p.AttrMaterial, p.AttrWeight, p.AttrBrand,
|
||||
).Scan(&p.ID, &p.CreatedAt, &p.UpdatedAt)
|
||||
}
|
||||
|
||||
@@ -105,9 +112,11 @@ func (r *ProductRepository) Update(ctx context.Context, p *models.Product) error
|
||||
tag, err := r.pool.Exec(ctx, `
|
||||
UPDATE products SET name = $1, description = $2, details = $3, price = $4,
|
||||
sale_price = NULLIF($5, 0), image_url = $6, category = $7, category_id = $8,
|
||||
is_active = $9, updated_at = NOW()
|
||||
WHERE id = $10
|
||||
`, p.Name, p.Description, p.Details, p.Price, p.SalePrice, p.ImageURL, p.Category, p.CategoryID, p.IsActive, p.ID)
|
||||
is_active = $9, attr_size = $10, attr_color = $11, attr_material = $12,
|
||||
attr_weight = $13, attr_brand = $14, updated_at = NOW()
|
||||
WHERE id = $15
|
||||
`, p.Name, p.Description, p.Details, p.Price, p.SalePrice, p.ImageURL, p.Category, p.CategoryID, p.IsActive,
|
||||
p.AttrSize, p.AttrColor, p.AttrMaterial, p.AttrWeight, p.AttrBrand, p.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -200,7 +209,9 @@ func (r *ProductRepository) collectProducts(rows pgx.Rows) ([]models.Product, er
|
||||
var catID sql.NullInt64
|
||||
if err := rows.Scan(
|
||||
&p.ID, &p.Name, &p.Description, &p.Details, &p.Price, &p.SalePrice,
|
||||
&p.ImageURL, &p.Category, &catID, &p.CategorySlug, &p.IsActive, &p.CreatedAt, &p.UpdatedAt,
|
||||
&p.ImageURL, &p.Category, &catID, &p.CategorySlug, &p.IsActive,
|
||||
&p.AttrSize, &p.AttrColor, &p.AttrMaterial, &p.AttrWeight, &p.AttrBrand,
|
||||
&p.CreatedAt, &p.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"shop/internal/models"
|
||||
)
|
||||
|
||||
type ReservationRepository struct {
|
||||
pool *pgxpool.Pool
|
||||
}
|
||||
|
||||
func NewReservationRepository(pool *pgxpool.Pool) *ReservationRepository {
|
||||
return &ReservationRepository{pool: pool}
|
||||
}
|
||||
|
||||
func (r *ReservationRepository) Create(ctx context.Context, res *models.Reservation) error {
|
||||
return r.pool.QueryRow(ctx, `
|
||||
INSERT INTO product_reservations (product_id, session_key, user_id, customer_name, customer_phone, expires_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id, created_at
|
||||
`, res.ProductID, res.SessionKey, res.UserID, res.CustomerName, res.CustomerPhone, res.ExpiresAt,
|
||||
).Scan(&res.ID, &res.CreatedAt)
|
||||
}
|
||||
|
||||
func (r *ReservationRepository) ActiveByProduct(ctx context.Context, productID int) (*models.Reservation, error) {
|
||||
var res models.Reservation
|
||||
var userID sql.NullInt64
|
||||
err := r.pool.QueryRow(ctx, `
|
||||
SELECT id, product_id, session_key, user_id, customer_name, customer_phone, expires_at, created_at
|
||||
FROM product_reservations
|
||||
WHERE product_id = $1 AND expires_at > NOW()
|
||||
ORDER BY created_at DESC
|
||||
LIMIT 1
|
||||
`, productID).Scan(
|
||||
&res.ID, &res.ProductID, &res.SessionKey, &userID,
|
||||
&res.CustomerName, &res.CustomerPhone, &res.ExpiresAt, &res.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if userID.Valid {
|
||||
id := int(userID.Int64)
|
||||
res.UserID = &id
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (r *ReservationRepository) ReplaceForProduct(ctx context.Context, res *models.Reservation) error {
|
||||
tx, err := r.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
_, err = tx.Exec(ctx, `DELETE FROM product_reservations WHERE product_id = $1`, res.ProductID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = tx.QueryRow(ctx, `
|
||||
INSERT INTO product_reservations (product_id, session_key, user_id, customer_name, customer_phone, expires_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING id, created_at
|
||||
`, res.ProductID, res.SessionKey, res.UserID, res.CustomerName, res.CustomerPhone, res.ExpiresAt,
|
||||
).Scan(&res.ID, &res.CreatedAt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
func (r *ReservationRepository) ExpiresIn() time.Duration {
|
||||
return 24 * time.Hour
|
||||
}
|
||||
Reference in New Issue
Block a user