package storage import ( "context" "time" "dana/internal/domain" ) // Banners отдаёт действующие баннеры: опубликованные и попадающие в период показа. func (s *Storage) Banners(ctx context.Context, position string) ([]domain.Banner, error) { rows, err := s.pool.Query(ctx, ` SELECT b.id, b.position, b.layout, b.theme, b.title, b.subtitle, b.badge, b.chips, COALESCE(mi.path, ''), COALESCE(mm.path, ''), b.link_url, b.button_text, b.disclaimer FROM banners b LEFT JOIN media mi ON mi.id = b.image_id LEFT JOIN media mm ON mm.id = b.mobile_image_id WHERE b.published AND (b.starts_at IS NULL OR b.starts_at <= now()) AND (b.ends_at IS NULL OR b.ends_at > now()) AND ($1 = '' OR b.position = $1) ORDER BY b.sort, b.id`, position) if err != nil { return nil, err } defer rows.Close() out := []domain.Banner{} for rows.Next() { var b domain.Banner if err := rows.Scan(&b.ID, &b.Position, &b.Layout, &b.Theme, &b.Title, &b.Subtitle, &b.Badge, &b.Chips, &b.Image, &b.MobileImage, &b.LinkURL, &b.ButtonText, &b.Disclaimer); err != nil { return nil, err } out = append(out, b) } return out, rows.Err() } // ---------- Админка ---------- type AdminBanner struct { ID int64 `json:"id"` Position string `json:"position"` Layout string `json:"layout"` Theme string `json:"theme"` Title string `json:"title"` Subtitle string `json:"subtitle"` Badge string `json:"badge"` Chips []string `json:"chips"` ImageID *int64 `json:"image_id"` Image string `json:"image,omitempty"` MobileImageID *int64 `json:"mobile_image_id"` MobileImage string `json:"mobile_image,omitempty"` LinkURL string `json:"link_url"` ButtonText string `json:"button_text"` Disclaimer string `json:"disclaimer"` StartsAt *time.Time `json:"starts_at"` EndsAt *time.Time `json:"ends_at"` Sort int `json:"sort"` Published bool `json:"published"` } func (s *Storage) AdminBanners(ctx context.Context, position string) ([]AdminBanner, error) { rows, err := s.pool.Query(ctx, ` SELECT b.id, b.position, b.layout, b.theme, b.title, b.subtitle, b.badge, b.chips, b.image_id, COALESCE(mi.path, ''), b.mobile_image_id, COALESCE(mm.path, ''), b.link_url, b.button_text, b.disclaimer, b.starts_at, b.ends_at, b.sort, b.published FROM banners b LEFT JOIN media mi ON mi.id = b.image_id LEFT JOIN media mm ON mm.id = b.mobile_image_id WHERE ($1 = '' OR b.position = $1) ORDER BY b.position, b.sort, b.id`, position) if err != nil { return nil, err } defer rows.Close() out := []AdminBanner{} for rows.Next() { var a AdminBanner if err := rows.Scan(&a.ID, &a.Position, &a.Layout, &a.Theme, &a.Title, &a.Subtitle, &a.Badge, &a.Chips, &a.ImageID, &a.Image, &a.MobileImageID, &a.MobileImage, &a.LinkURL, &a.ButtonText, &a.Disclaimer, &a.StartsAt, &a.EndsAt, &a.Sort, &a.Published); err != nil { return nil, err } out = append(out, a) } return out, rows.Err() } type BannerInput struct { Position string `json:"position"` Layout string `json:"layout"` Theme string `json:"theme"` Title string `json:"title"` Subtitle string `json:"subtitle"` Badge string `json:"badge"` Chips []string `json:"chips"` ImageID *int64 `json:"image_id"` MobileImageID *int64 `json:"mobile_image_id"` LinkURL string `json:"link_url"` ButtonText string `json:"button_text"` Disclaimer string `json:"disclaimer"` StartsAt *time.Time `json:"starts_at"` EndsAt *time.Time `json:"ends_at"` Sort int `json:"sort"` Published bool `json:"published"` } func (s *Storage) AdminCreateBanner(ctx context.Context, in BannerInput) (int64, error) { var id int64 err := s.pool.QueryRow(ctx, ` INSERT INTO banners (position, layout, theme, title, subtitle, badge, chips, image_id, mobile_image_id, link_url, button_text, disclaimer, starts_at, ends_at, sort, published) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16) RETURNING id`, in.Position, in.Layout, in.Theme, in.Title, in.Subtitle, in.Badge, in.Chips, in.ImageID, in.MobileImageID, in.LinkURL, in.ButtonText, in.Disclaimer, in.StartsAt, in.EndsAt, in.Sort, in.Published).Scan(&id) return id, err } func (s *Storage) AdminUpdateBanner(ctx context.Context, id int64, in BannerInput) error { tag, err := s.pool.Exec(ctx, ` UPDATE banners SET position=$2, layout=$3, theme=$4, title=$5, subtitle=$6, badge=$7, chips=$8, image_id=$9, mobile_image_id=$10, link_url=$11, button_text=$12, disclaimer=$13, starts_at=$14, ends_at=$15, sort=$16, published=$17, updated_at=now() WHERE id=$1`, id, in.Position, in.Layout, in.Theme, in.Title, in.Subtitle, in.Badge, in.Chips, in.ImageID, in.MobileImageID, in.LinkURL, in.ButtonText, in.Disclaimer, in.StartsAt, in.EndsAt, in.Sort, in.Published) if err != nil { return err } if tag.RowsAffected() == 0 { return ErrNotFound } return nil } func (s *Storage) AdminDeleteBanner(ctx context.Context, id int64) error { tag, err := s.pool.Exec(ctx, `DELETE FROM banners WHERE id=$1`, id) if err != nil { return err } if tag.RowsAffected() == 0 { return ErrNotFound } return nil }