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

125 lines
4.1 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"
)
// ErrInUse — запись нельзя удалить, пока на неё ссылаются (например, специальность с врачами).
var ErrInUse = errors.New("storage: in use")
type AdminSpecialty struct {
ID int64 `json:"id"`
Slug string `json:"slug"`
Name string `json:"name"`
NamePlural string `json:"name_plural"`
Sort int `json:"sort"`
Published bool `json:"published"`
Doctors int `json:"doctors"` // все врачи раздела, включая скрытых
}
type SpecialtyInput struct {
Slug string
Name string
NamePlural string
Sort int
}
func (s *Storage) AdminSpecialties(ctx context.Context) ([]AdminSpecialty, error) {
rows, err := s.pool.Query(ctx, `
SELECT sp.id, sp.slug, sp.name, sp.name_plural, sp.sort, sp.published,
(SELECT count(*) FROM doctor_specialties ds
JOIN doctors d ON d.id = ds.doctor_id AND d.deleted_at IS NULL
WHERE ds.specialty_id = sp.id)
FROM specialties sp
ORDER BY sp.sort, sp.name_plural, sp.id`)
if err != nil {
return nil, err
}
defer rows.Close()
var out []AdminSpecialty
for rows.Next() {
var sp AdminSpecialty
if err := rows.Scan(&sp.ID, &sp.Slug, &sp.Name, &sp.NamePlural, &sp.Sort, &sp.Published, &sp.Doctors); err != nil {
return nil, err
}
out = append(out, sp)
}
return out, rows.Err()
}
func (s *Storage) AdminCreateSpecialty(ctx context.Context, in SpecialtyInput) (int64, error) {
var id int64
err := s.pool.QueryRow(ctx, `
INSERT INTO specialties (slug, name, name_plural, sort) VALUES ($1, $2, $3, $4) RETURNING id`,
in.Slug, in.Name, in.NamePlural, in.Sort).Scan(&id)
return id, wrapConflict(err)
}
// AdminUpdateSpecialty меняет название и порядок. Адрес раздела не меняется:
// он входит в адреса страниц всех врачей раздела.
func (s *Storage) AdminUpdateSpecialty(ctx context.Context, id int64, in SpecialtyInput) error {
tag, err := s.pool.Exec(ctx, `UPDATE specialties SET name=$2, name_plural=$3, sort=$4 WHERE id=$1`,
id, in.Name, in.NamePlural, in.Sort)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
return ErrNotFound
}
return nil
}
// AdminDeleteSpecialty удаляет только пустой раздел: иначе у врачей пропала бы специальность
// (связи удаляются каскадом) и сменились бы адреса их страниц.
func (s *Storage) AdminDeleteSpecialty(ctx context.Context, id int64) error {
tag, err := s.pool.Exec(ctx, `
DELETE FROM specialties sp WHERE sp.id = $1
AND NOT EXISTS (SELECT 1 FROM doctor_specialties ds WHERE ds.specialty_id = sp.id)`, id)
if err != nil {
return err
}
if tag.RowsAffected() == 0 {
var exists bool
if err := s.pool.QueryRow(ctx, `SELECT EXISTS (SELECT 1 FROM specialties WHERE id=$1)`, id).Scan(&exists); err != nil {
return err
}
if exists {
return ErrInUse
}
return ErrNotFound
}
return nil
}
type NewDoctor struct {
Slug string
LastName string
FirstName string
MiddleName string
Position string
SpecialtyID int64
}
// AdminCreateDoctor заводит врача скрытым: на сайт он попадёт, когда карточку заполнят и включат.
func (s *Storage) AdminCreateDoctor(ctx context.Context, in NewDoctor) (int64, error) {
tx, err := s.pool.Begin(ctx)
if err != nil {
return 0, err
}
defer tx.Rollback(ctx) //nolint:errcheck // после Commit откат ничего не делает
var id int64
if err := tx.QueryRow(ctx, `
INSERT INTO doctors (slug, last_name, first_name, middle_name, position, published)
VALUES ($1, $2, $3, $4, $5, false) RETURNING id`,
in.Slug, in.LastName, in.FirstName, in.MiddleName, in.Position).Scan(&id); err != nil {
return 0, wrapConflict(err)
}
if _, err := tx.Exec(ctx, `
INSERT INTO doctor_specialties (doctor_id, specialty_id, is_primary) VALUES ($1, $2, true)`,
id, in.SpecialtyID); err != nil {
return 0, wrapConflict(err)
}
return id, tx.Commit(ctx)
}