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

222 lines
7.0 KiB
Go
Raw 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"
"errors"
"github.com/jackc/pgx/v5"
)
// ---------- Услуги ----------
type AdminService struct {
ID int64 `json:"id"`
ParentID *int64 `json:"parent_id"`
ParentName string `json:"parent_name,omitempty"`
Slug string `json:"slug"`
Path string `json:"path"`
Name string `json:"name"`
ShortName string `json:"short_name"`
Lead string `json:"lead"`
Sort int `json:"sort"`
Published bool `json:"published"`
SEOTitle string `json:"seo_title"`
SEODesc string `json:"seo_description"`
SEOH1 string `json:"seo_h1"`
PriceFrom *float64 `json:"price_from,omitempty"`
PriceCount int `json:"price_count"`
}
// AdminServices возвращает плоский список услуг, включая скрытые.
func (s *Storage) AdminServices(ctx context.Context, q string) ([]AdminService, error) {
rows, err := s.pool.Query(ctx, `
SELECT s.id, s.parent_id, COALESCE(p.name, ''), s.slug, s.path, s.name,
s.short_name, s.lead, s.sort, s.published,
s.seo_title, s.seo_description, s.seo_h1,
(SELECT min(pi.price) FROM price_items pi
WHERE pi.service_id = s.id AND pi.published AND NOT pi.exclude_from_min),
(SELECT count(*) FROM price_items pi WHERE pi.service_id = s.id)
FROM services s
LEFT JOIN services p ON p.id = s.parent_id
WHERE s.deleted_at IS NULL
AND ($1 = '' OR s.name ILIKE '%' || $1 || '%' OR s.path ILIKE '%' || $1 || '%')
ORDER BY COALESCE(p.sort, s.sort), COALESCE(p.name, s.name), s.parent_id NULLS FIRST, s.sort, s.name`, q)
if err != nil {
return nil, err
}
defer rows.Close()
var out []AdminService
for rows.Next() {
var a AdminService
if err := rows.Scan(&a.ID, &a.ParentID, &a.ParentName, &a.Slug, &a.Path, &a.Name,
&a.ShortName, &a.Lead, &a.Sort, &a.Published,
&a.SEOTitle, &a.SEODesc, &a.SEOH1, &a.PriceFrom, &a.PriceCount); err != nil {
return nil, err
}
out = append(out, a)
}
return out, rows.Err()
}
type ServiceUpdate struct {
Name string `json:"name"`
ShortName string `json:"short_name"`
Lead string `json:"lead"`
Sort int `json:"sort"`
Published bool `json:"published"`
SEOTitle string `json:"seo_title"`
SEODesc string `json:"seo_description"`
SEOH1 string `json:"seo_h1"`
}
func (s *Storage) AdminUpdateService(ctx context.Context, id int64, u ServiceUpdate) error {
tag, err := s.pool.Exec(ctx, `
UPDATE services SET name=$2, short_name=$3, lead=$4, sort=$5, published=$6,
seo_title=$7, seo_description=$8, seo_h1=$9, updated_at=now()
WHERE id=$1 AND deleted_at IS NULL`,
id, u.Name, u.ShortName, u.Lead, u.Sort, u.Published, u.SEOTitle, u.SEODesc, u.SEOH1)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrNotFound
}
return nil
}
// ---------- Прайс ----------
type AdminPriceSection struct {
ID int64 `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
Count int `json:"count"`
}
func (s *Storage) AdminPriceSections(ctx context.Context) ([]AdminPriceSection, error) {
rows, err := s.pool.Query(ctx, `
SELECT ps.id, ps.slug, ps.name, (SELECT count(*) FROM price_items pi WHERE pi.section_id = ps.id)
FROM price_sections ps
ORDER BY ps.sort, ps.name`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []AdminPriceSection
for rows.Next() {
var a AdminPriceSection
if err := rows.Scan(&a.ID, &a.Slug, &a.Name, &a.Count); err != nil {
return nil, err
}
out = append(out, a)
}
return out, rows.Err()
}
type AdminPriceItem struct {
ID int64 `json:"id"`
SectionID int64 `json:"section_id"`
SectionName string `json:"section_name"`
ServiceID *int64 `json:"service_id"`
ServiceName string `json:"service_name,omitempty"`
Name string `json:"name"`
Price float64 `json:"price"`
Unit string `json:"unit"`
PriceFrom bool `json:"price_from"`
Sort int `json:"sort"`
Published bool `json:"published"`
}
// AdminPriceItems: постранично, с поиском и фильтром по разделу.
func (s *Storage) AdminPriceItems(ctx context.Context, sectionID int64, q string, limit, offset int) ([]AdminPriceItem, int, error) {
var total int
if err := s.pool.QueryRow(ctx, `
SELECT count(*) FROM price_items pi
WHERE ($1 = 0 OR pi.section_id = $1)
AND ($2 = '' OR pi.name ILIKE '%' || $2 || '%')`, sectionID, q).Scan(&total); err != nil {
return nil, 0, err
}
rows, err := s.pool.Query(ctx, `
SELECT pi.id, pi.section_id, ps.name, pi.service_id, COALESCE(sv.name, ''),
pi.name, pi.price, pi.unit, pi.price_from, pi.sort, pi.published
FROM price_items pi
JOIN price_sections ps ON ps.id = pi.section_id
LEFT JOIN services sv ON sv.id = pi.service_id
WHERE ($1 = 0 OR pi.section_id = $1)
AND ($2 = '' OR pi.name ILIKE '%' || $2 || '%')
ORDER BY ps.sort, pi.sort, pi.name
LIMIT $3 OFFSET $4`, sectionID, q, limit, offset)
if err != nil {
return nil, 0, err
}
defer rows.Close()
var out []AdminPriceItem
for rows.Next() {
var a AdminPriceItem
if err := rows.Scan(&a.ID, &a.SectionID, &a.SectionName, &a.ServiceID, &a.ServiceName,
&a.Name, &a.Price, &a.Unit, &a.PriceFrom, &a.Sort, &a.Published); err != nil {
return nil, 0, err
}
out = append(out, a)
}
return out, total, rows.Err()
}
type PriceItemInput struct {
SectionID int64 `json:"section_id"`
ServiceID *int64 `json:"service_id"`
Name string `json:"name"`
Price float64 `json:"price"`
Unit string `json:"unit"`
PriceFrom bool `json:"price_from"`
Sort int `json:"sort"`
Published bool `json:"published"`
}
func (s *Storage) AdminUpdatePriceItem(ctx context.Context, id int64, in PriceItemInput) error {
tag, err := s.pool.Exec(ctx, `
UPDATE price_items SET section_id=$2, service_id=$3, name=$4, price=$5,
unit=$6, price_from=$7, sort=$8, published=$9, updated_at=now()
WHERE id=$1`,
id, in.SectionID, in.ServiceID, in.Name, in.Price, in.Unit, in.PriceFrom, in.Sort, in.Published)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrNotFound
}
return nil
}
func (s *Storage) AdminCreatePriceItem(ctx context.Context, in PriceItemInput) (int64, error) {
var id int64
err := s.pool.QueryRow(ctx, `
INSERT INTO price_items (section_id, service_id, name, price, unit, price_from, sort, published)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING id`,
in.SectionID, in.ServiceID, in.Name, in.Price, in.Unit, in.PriceFrom, in.Sort, in.Published).Scan(&id)
return id, err
}
func (s *Storage) AdminDeletePriceItem(ctx context.Context, id int64) error {
tag, err := s.pool.Exec(ctx, `DELETE FROM price_items WHERE id=$1`, id)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrNotFound
}
return nil
}
func (s *Storage) priceItemExists(ctx context.Context, id int64) error {
var x int
err := s.pool.QueryRow(ctx, `SELECT 1 FROM price_items WHERE id=$1`, id).Scan(&x)
if errors.Is(err, pgx.ErrNoRows) {
return ErrNotFound
}
return err
}