package repository import ( "context" "regexp" "strings" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgxpool" "shop/internal/models" ) type CategoryRepository struct { pool *pgxpool.Pool } func NewCategoryRepository(pool *pgxpool.Pool) *CategoryRepository { return &CategoryRepository{pool: pool} } var slugRe = regexp.MustCompile(`[^a-z0-9]+`) func Slugify(name string) string { translit := map[rune]string{ 'а': "a", 'б': "b", 'в': "v", 'г': "g", 'д': "d", 'е': "e", 'ё': "e", 'ж': "zh", 'з': "z", 'и': "i", 'й': "y", 'к': "k", 'л': "l", 'м': "m", 'н': "n", 'о': "o", 'п': "p", 'р': "r", 'с': "s", 'т': "t", 'у': "u", 'ф': "f", 'х': "h", 'ц': "ts", 'ч': "ch", 'ш': "sh", 'щ': "sch", 'ъ': "", 'ы': "y", 'ь': "", 'э': "e", 'ю': "yu", 'я': "ya", } var b strings.Builder for _, r := range strings.ToLower(name) { if t, ok := translit[r]; ok { b.WriteString(t) } else if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') { b.WriteRune(r) } else if r == ' ' || r == '-' { b.WriteRune('-') } } s := slugRe.ReplaceAllString(b.String(), "-") return strings.Trim(s, "-") } func (r *CategoryRepository) All(ctx context.Context) ([]models.Category, error) { rows, err := r.pool.Query(ctx, ` SELECT c.id, c.name, c.slug, c.description, c.sort_order, COUNT(p.id) FILTER (WHERE p.is_active = true) AS cnt FROM categories c LEFT JOIN products p ON p.category_id = c.id GROUP BY c.id ORDER BY c.sort_order, c.name `) if err != nil { return nil, err } defer rows.Close() var list []models.Category for rows.Next() { var c models.Category if err := rows.Scan(&c.ID, &c.Name, &c.Slug, &c.Description, &c.SortOrder, &c.Count); err != nil { return nil, err } list = append(list, c) } return list, rows.Err() } func (r *CategoryRepository) GetBySlug(ctx context.Context, slug string) (models.Category, error) { var c models.Category err := r.pool.QueryRow(ctx, ` SELECT id, name, slug, description, sort_order FROM categories WHERE slug = $1 `, slug).Scan(&c.ID, &c.Name, &c.Slug, &c.Description, &c.SortOrder) return c, err } func (r *CategoryRepository) GetByID(ctx context.Context, id int) (models.Category, error) { var c models.Category err := r.pool.QueryRow(ctx, ` SELECT id, name, slug, description, sort_order FROM categories WHERE id = $1 `, id).Scan(&c.ID, &c.Name, &c.Slug, &c.Description, &c.SortOrder) return c, err } func (r *CategoryRepository) Create(ctx context.Context, name, description string) (models.Category, error) { slug := Slugify(name) var c models.Category err := r.pool.QueryRow(ctx, ` INSERT INTO categories (name, slug, description) VALUES ($1, $2, $3) ON CONFLICT (slug) DO UPDATE SET name = EXCLUDED.name RETURNING id, name, slug, description, sort_order `, name, slug, description).Scan(&c.ID, &c.Name, &c.Slug, &c.Description, &c.SortOrder) return c, err } func (r *CategoryRepository) Update(ctx context.Context, c *models.Category) error { tag, err := r.pool.Exec(ctx, ` UPDATE categories SET name = $1, slug = $2, description = $3, sort_order = $4 WHERE id = $5 `, c.Name, c.Slug, c.Description, c.SortOrder, c.ID) if err != nil { return err } if tag.RowsAffected() == 0 { return pgx.ErrNoRows } return nil } func (r *CategoryRepository) Delete(ctx context.Context, id int) error { _, err := r.pool.Exec(ctx, `UPDATE products SET category_id = NULL WHERE category_id = $1`, id) if err != nil { return err } tag, err := r.pool.Exec(ctx, `DELETE FROM categories WHERE id = $1`, id) if err != nil { return err } if tag.RowsAffected() == 0 { return pgx.ErrNoRows } return nil }