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

117 lines
4.0 KiB
Go

package storage
import (
"context"
"time"
)
type AdminPromo struct {
ID int64 `json:"id"`
Slug string `json:"slug"`
Title string `json:"title"`
Lead string `json:"lead"`
ImageID *int64 `json:"image_id"`
Image string `json:"image,omitempty"`
DiscountPercent *int `json:"discount_percent"`
Terms string `json:"terms"`
CheckupID *int64 `json:"checkup_id"`
CheckupName string `json:"checkup_name,omitempty"`
StartsAt *time.Time `json:"starts_at"`
EndsAt *time.Time `json:"ends_at"`
Published bool `json:"published"`
PublishedAt time.Time `json:"published_at"`
SEOTitle string `json:"seo_title"`
SEODesc string `json:"seo_description"`
SEOH1 string `json:"seo_h1"`
}
// AdminPromos — все акции, включая скрытые и завершённые.
func (s *Storage) AdminPromos(ctx context.Context) ([]AdminPromo, error) {
rows, err := s.pool.Query(ctx, `
SELECT p.id, p.slug, p.title, p.lead, p.image_id, COALESCE(m.path, ''),
p.discount_percent, p.terms, p.checkup_id, COALESCE(c.name, ''),
p.starts_at, p.ends_at, p.published, p.published_at,
p.seo_title, p.seo_description, p.seo_h1
FROM posts p
LEFT JOIN media m ON m.id = p.image_id
LEFT JOIN checkups c ON c.id = p.checkup_id
WHERE p.kind = 'promo' AND p.deleted_at IS NULL
ORDER BY p.published_at DESC, p.id DESC`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []AdminPromo
for rows.Next() {
var a AdminPromo
if err := rows.Scan(&a.ID, &a.Slug, &a.Title, &a.Lead, &a.ImageID, &a.Image,
&a.DiscountPercent, &a.Terms, &a.CheckupID, &a.CheckupName,
&a.StartsAt, &a.EndsAt, &a.Published, &a.PublishedAt,
&a.SEOTitle, &a.SEODesc, &a.SEOH1); err != nil {
return nil, err
}
out = append(out, a)
}
return out, rows.Err()
}
type PromoInput struct {
Slug string
Title string
Lead string
ImageID *int64
DiscountPercent *int
Terms string
CheckupID *int64
StartsAt *time.Time
EndsAt *time.Time
Published bool
SEOTitle string
SEODesc string
SEOH1 string
}
func (s *Storage) AdminCreatePromo(ctx context.Context, in PromoInput) (int64, error) {
var id int64
err := s.pool.QueryRow(ctx, `
INSERT INTO posts (kind, slug, title, lead, image_id, discount_percent, terms, checkup_id,
starts_at, ends_at, published, seo_title, seo_description, seo_h1)
VALUES ('promo', $1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)
RETURNING id`,
in.Slug, in.Title, in.Lead, in.ImageID, in.DiscountPercent, in.Terms, in.CheckupID,
in.StartsAt, in.EndsAt, in.Published, in.SEOTitle, in.SEODesc, in.SEOH1).Scan(&id)
return id, wrapConflict(err)
}
func (s *Storage) AdminUpdatePromo(ctx context.Context, id int64, in PromoInput) error {
tag, err := s.pool.Exec(ctx, `
UPDATE posts SET slug=$2, title=$3, lead=$4, image_id=$5, discount_percent=$6, terms=$7,
checkup_id=$8, starts_at=$9, ends_at=$10, published=$11,
seo_title=$12, seo_description=$13, seo_h1=$14, updated_at=now()
WHERE id=$1 AND kind='promo' AND deleted_at IS NULL`,
id, in.Slug, in.Title, in.Lead, in.ImageID, in.DiscountPercent, in.Terms,
in.CheckupID, in.StartsAt, in.EndsAt, in.Published, in.SEOTitle, in.SEODesc, in.SEOH1)
if err != nil {
return wrapConflict(err)
}
if tag.RowsAffected() == 0 {
return ErrNotFound
}
return nil
}
// AdminDeletePromo скрывает акцию мягким удалением: запись остаётся для истории и аудита.
func (s *Storage) AdminDeletePromo(ctx context.Context, id int64) error {
tag, err := s.pool.Exec(ctx, `
UPDATE posts SET deleted_at=now(), published=false, updated_at=now()
WHERE id=$1 AND kind='promo' AND deleted_at IS NULL`, id)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrNotFound
}
return nil
}