package repository import ( "context" "errors" "fmt" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "shop/internal/models" ) type ProductRepository struct { pool *pgxpool.Pool } func NewProductRepository(pool *pgxpool.Pool) *ProductRepository { return &ProductRepository{pool: pool} } func (r *ProductRepository) scanProduct(row pgx.Row) (models.Product, error) { var p models.Product err := row.Scan( &p.ID, &p.Name, &p.Description, &p.Details, &p.Price, &p.ImageURL, &p.Category, &p.IsActive, &p.CreatedAt, &p.UpdatedAt, ) return p, err } const productColumns = `id, name, description, details, price, image_url, category, is_active, created_at, updated_at` func (r *ProductRepository) Featured(ctx context.Context, limit int) ([]models.Product, error) { rows, err := r.pool.Query(ctx, ` SELECT `+productColumns+` FROM products WHERE is_active = true ORDER BY created_at DESC LIMIT $1 `, limit) if err != nil { return nil, err } defer rows.Close() return r.collectProducts(rows) } func (r *ProductRepository) All(ctx context.Context) ([]models.Product, error) { rows, err := r.pool.Query(ctx, ` SELECT `+productColumns+` FROM products ORDER BY created_at DESC `) if err != nil { return nil, err } defer rows.Close() return r.collectProducts(rows) } func (r *ProductRepository) GetByID(ctx context.Context, id int) (models.Product, error) { row := r.pool.QueryRow(ctx, `SELECT `+productColumns+` FROM products WHERE id = $1`, id) p, err := r.scanProduct(row) if err != nil { return models.Product{}, err } p.Images, err = r.ImagesByProduct(ctx, id) return p, err } func (r *ProductRepository) Create(ctx context.Context, p *models.Product) error { return r.pool.QueryRow(ctx, ` INSERT INTO products (name, description, details, price, image_url, category, is_active) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id, created_at, updated_at `, p.Name, p.Description, p.Details, p.Price, p.ImageURL, p.Category, p.IsActive, ).Scan(&p.ID, &p.CreatedAt, &p.UpdatedAt) } 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, image_url = $5, category = $6, is_active = $7, updated_at = NOW() WHERE id = $8 `, p.Name, p.Description, p.Details, p.Price, p.ImageURL, p.Category, p.IsActive, p.ID) if err != nil { return err } if tag.RowsAffected() == 0 { return pgx.ErrNoRows } return nil } func (r *ProductRepository) Delete(ctx context.Context, id int) error { tag, err := r.pool.Exec(ctx, `DELETE FROM products WHERE id = $1`, id) if err != nil { return err } if tag.RowsAffected() == 0 { return pgx.ErrNoRows } return nil } func (r *ProductRepository) Categories(ctx context.Context) ([]models.Category, error) { rows, err := r.pool.Query(ctx, ` SELECT category, COUNT(*) AS cnt FROM products GROUP BY category ORDER BY cnt DESC `) if err != nil { return nil, err } defer rows.Close() var categories []models.Category for rows.Next() { var c models.Category if err := rows.Scan(&c.Name, &c.Count); err != nil { return nil, err } c.Slug = c.Name categories = append(categories, c) } return categories, rows.Err() } func (r *ProductRepository) ImagesByProduct(ctx context.Context, productID int) ([]models.ProductImage, error) { rows, err := r.pool.Query(ctx, ` SELECT id, product_id, file_path, is_primary, sort_order, created_at FROM product_images WHERE product_id = $1 ORDER BY is_primary DESC, sort_order ASC, id ASC `, productID) if err != nil { return nil, err } defer rows.Close() var images []models.ProductImage for rows.Next() { var img models.ProductImage if err := rows.Scan(&img.ID, &img.ProductID, &img.FilePath, &img.IsPrimary, &img.SortOrder, &img.CreatedAt); err != nil { return nil, err } images = append(images, img) } return images, rows.Err() } func (r *ProductRepository) AddImage(ctx context.Context, img *models.ProductImage) error { return r.pool.QueryRow(ctx, ` INSERT INTO product_images (product_id, file_path, is_primary, sort_order) VALUES ($1, $2, $3, $4) RETURNING id, created_at `, img.ProductID, img.FilePath, img.IsPrimary, img.SortOrder, ).Scan(&img.ID, &img.CreatedAt) } func (r *ProductRepository) DeleteImage(ctx context.Context, imageID, productID int) (string, error) { var path string err := r.pool.QueryRow(ctx, ` DELETE FROM product_images WHERE id = $1 AND product_id = $2 RETURNING file_path `, imageID, productID).Scan(&path) if errors.Is(err, pgx.ErrNoRows) { return "", fmt.Errorf("image not found") } return path, err } func (r *ProductRepository) collectProducts(rows pgx.Rows) ([]models.Product, error) { var products []models.Product for rows.Next() { var p models.Product if err := rows.Scan( &p.ID, &p.Name, &p.Description, &p.Details, &p.Price, &p.ImageURL, &p.Category, &p.IsActive, &p.CreatedAt, &p.UpdatedAt, ); err != nil { return nil, err } products = append(products, p) } return products, rows.Err() }