125 lines
4.1 KiB
Go
125 lines
4.1 KiB
Go
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)
|
||
}
|