dana-medical-site/internal/storage/admin_checkups.go
2026-10-14 13:33:13 +00:00

160 lines
4.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}