package storage import ( "context" "time" ) type AdminDocument struct { ID int64 `json:"id"` Kind string `json:"kind"` Title string `json:"title"` Body string `json:"body"` FileID *int64 `json:"file_id"` File string `json:"file,omitempty"` FileMime string `json:"file_mime,omitempty"` PreviewID *int64 `json:"preview_id"` Preview string `json:"preview,omitempty"` IssuedAt *time.Time `json:"issued_at"` Sort int `json:"sort"` Published bool `json:"published"` } func (s *Storage) AdminDocuments(ctx context.Context) ([]AdminDocument, error) { rows, err := s.pool.Query(ctx, ` SELECT d.id, d.kind, d.title, d.body, d.file_id, COALESCE(f.path, ''), COALESCE(f.mime, ''), d.preview_id, COALESCE(p.path, ''), d.issued_at, d.sort, d.published FROM documents d LEFT JOIN media f ON f.id = d.file_id LEFT JOIN media p ON p.id = d.preview_id ORDER BY d.kind, d.sort, d.id`) if err != nil { return nil, err } defer rows.Close() var out []AdminDocument for rows.Next() { var d AdminDocument if err := rows.Scan(&d.ID, &d.Kind, &d.Title, &d.Body, &d.FileID, &d.File, &d.FileMime, &d.PreviewID, &d.Preview, &d.IssuedAt, &d.Sort, &d.Published); err != nil { return nil, err } out = append(out, d) } return out, rows.Err() } type DocumentInput struct { Kind string Title string Body string FileID *int64 PreviewID *int64 IssuedAt *time.Time Sort int Published bool } func (s *Storage) AdminCreateDocument(ctx context.Context, in DocumentInput) (int64, error) { var id int64 err := s.pool.QueryRow(ctx, ` INSERT INTO documents (kind, title, body, file_id, preview_id, issued_at, sort, published) VALUES ($1,$2,$3,$4,$5,$6,$7,$8) RETURNING id`, in.Kind, in.Title, in.Body, in.FileID, in.PreviewID, in.IssuedAt, in.Sort, in.Published).Scan(&id) return id, wrapConflict(err) } func (s *Storage) AdminUpdateDocument(ctx context.Context, id int64, in DocumentInput) error { tag, err := s.pool.Exec(ctx, ` UPDATE documents SET kind=$2, title=$3, body=$4, file_id=$5, preview_id=$6, issued_at=$7, sort=$8, published=$9 WHERE id=$1`, id, in.Kind, in.Title, in.Body, in.FileID, in.PreviewID, in.IssuedAt, in.Sort, in.Published) if err != nil { return wrapConflict(err) } if tag.RowsAffected() == 0 { return ErrNotFound } return nil } // AdminDeleteDocument удаляет запись; файлы остаются в медиатеке. func (s *Storage) AdminDeleteDocument(ctx context.Context, id int64) error { tag, err := s.pool.Exec(ctx, `DELETE FROM documents WHERE id=$1`, id) if err != nil { return err } if tag.RowsAffected() == 0 { return ErrNotFound } return nil }