package storage import ( "context" "encoding/json" "github.com/jackc/pgx/v5" ) type AdminCheckup struct { ID int64 `json:"id"` Slug string `json:"slug"` Audience string `json:"audience"` Name string `json:"name"` Lead string `json:"lead"` Blocks json.RawMessage `json:"blocks"` Price *float64 `json:"price"` OldPrice *float64 `json:"old_price"` ImageID *int64 `json:"image_id"` Image string `json:"image,omitempty"` Items []string `json:"items"` Sort int `json:"sort"` Published bool `json:"published"` SEOTitle string `json:"seo_title"` SEODesc string `json:"seo_description"` SEOH1 string `json:"seo_h1"` } // AdminCheckups — все программы со составом. Blocks отдаём, чтобы админка могла // предложить состав из старого описания, пока его не сохранили списком. func (s *Storage) AdminCheckups(ctx context.Context) ([]AdminCheckup, error) { rows, err := s.pool.Query(ctx, ` SELECT c.id, c.slug, c.audience, c.name, c.lead, c.blocks, c.price, c.old_price, c.image_id, COALESCE(m.path, ''), c.sort, c.published, c.seo_title, c.seo_description, c.seo_h1 FROM checkups c LEFT JOIN media m ON m.id = c.image_id WHERE c.deleted_at IS NULL ORDER BY c.audience, c.sort, c.name`) if err != nil { return nil, err } defer rows.Close() var out []AdminCheckup index := map[int64]int{} for rows.Next() { var a AdminCheckup if err := rows.Scan(&a.ID, &a.Slug, &a.Audience, &a.Name, &a.Lead, &a.Blocks, &a.Price, &a.OldPrice, &a.ImageID, &a.Image, &a.Sort, &a.Published, &a.SEOTitle, &a.SEODesc, &a.SEOH1); err != nil { return nil, err } a.Items = []string{} index[a.ID] = len(out) out = append(out, a) } if err := rows.Err(); err != nil { return nil, err } itemRows, err := s.pool.Query(ctx, `SELECT checkup_id, name FROM checkup_items ORDER BY checkup_id, sort, id`) if err != nil { return nil, err } defer itemRows.Close() for itemRows.Next() { var checkupID int64 var name string if err := itemRows.Scan(&checkupID, &name); err != nil { return nil, err } if i, found := index[checkupID]; found { out[i].Items = append(out[i].Items, name) } } return out, itemRows.Err() } type CheckupInput struct { Slug string Audience string Name string Lead string Price *float64 OldPrice *float64 ImageID *int64 Items []string Sort int Published bool SEOTitle string SEODesc string SEOH1 string } func (s *Storage) AdminCreateCheckup(ctx context.Context, in CheckupInput) (int64, error) { tx, err := s.pool.Begin(ctx) if err != nil { return 0, err } defer tx.Rollback(ctx) //nolint:errcheck // после Commit откат ничего не делает var id int64 err = tx.QueryRow(ctx, ` INSERT INTO checkups (slug, audience, name, lead, price, old_price, image_id, sort, published, seo_title, seo_description, seo_h1) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) RETURNING id`, in.Slug, in.Audience, in.Name, in.Lead, in.Price, in.OldPrice, in.ImageID, in.Sort, in.Published, in.SEOTitle, in.SEODesc, in.SEOH1).Scan(&id) if err != nil { return 0, wrapConflict(err) } if err := replaceCheckupItems(ctx, tx, id, in.Items); err != nil { return 0, err } return id, tx.Commit(ctx) } func (s *Storage) AdminUpdateCheckup(ctx context.Context, id int64, in CheckupInput) error { tx, err := s.pool.Begin(ctx) if err != nil { return err } defer tx.Rollback(ctx) //nolint:errcheck // после Commit откат ничего не делает tag, err := tx.Exec(ctx, ` UPDATE checkups SET slug=$2, audience=$3, name=$4, lead=$5, price=$6, old_price=$7, image_id=$8, sort=$9, published=$10, seo_title=$11, seo_description=$12, seo_h1=$13, updated_at=now() WHERE id=$1 AND deleted_at IS NULL`, id, in.Slug, in.Audience, in.Name, in.Lead, in.Price, in.OldPrice, in.ImageID, in.Sort, in.Published, in.SEOTitle, in.SEODesc, in.SEOH1) if err != nil { return wrapConflict(err) } if tag.RowsAffected() == 0 { return ErrNotFound } if err := replaceCheckupItems(ctx, tx, id, in.Items); err != nil { return err } return tx.Commit(ctx) } // replaceCheckupItems перезаписывает состав целиком: порядок строк в админке — порядок на сайте. func replaceCheckupItems(ctx context.Context, tx pgx.Tx, checkupID int64, items []string) error { if _, err := tx.Exec(ctx, `DELETE FROM checkup_items WHERE checkup_id = $1`, checkupID); err != nil { return err } for i, name := range items { if _, err := tx.Exec(ctx, `INSERT INTO checkup_items (checkup_id, name, sort) VALUES ($1, $2, $3)`, checkupID, name, (i+1)*10); err != nil { return err } } return nil }