108 lines
3.6 KiB
Go
108 lines
3.6 KiB
Go
package storage
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"time"
|
||
)
|
||
|
||
// AdminPost — новость или статья для админки, включая скрытые.
|
||
type AdminPost struct {
|
||
ID int64 `json:"id"`
|
||
Kind string `json:"kind"`
|
||
Slug string `json:"slug"`
|
||
Title string `json:"title"`
|
||
Lead string `json:"lead"`
|
||
Blocks json.RawMessage `json:"blocks"`
|
||
ImageID *int64 `json:"image_id"`
|
||
Image string `json:"image,omitempty"`
|
||
Published bool `json:"published"`
|
||
PublishedAt time.Time `json:"published_at"`
|
||
SEOTitle string `json:"seo_title"`
|
||
SEODesc string `json:"seo_description"`
|
||
SEOH1 string `json:"seo_h1"`
|
||
}
|
||
|
||
// AdminPosts — публикации одного вида (news | article), новые сверху.
|
||
func (s *Storage) AdminPosts(ctx context.Context, kind string) ([]AdminPost, error) {
|
||
rows, err := s.pool.Query(ctx, `
|
||
SELECT p.id, p.kind, p.slug, p.title, p.lead, p.blocks, p.image_id, COALESCE(m.path, ''),
|
||
p.published, p.published_at, p.seo_title, p.seo_description, p.seo_h1
|
||
FROM posts p
|
||
LEFT JOIN media m ON m.id = p.image_id
|
||
WHERE p.kind = $1 AND p.deleted_at IS NULL
|
||
ORDER BY p.published_at DESC, p.id DESC`, kind)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
|
||
var out []AdminPost
|
||
for rows.Next() {
|
||
var a AdminPost
|
||
if err := rows.Scan(&a.ID, &a.Kind, &a.Slug, &a.Title, &a.Lead, &a.Blocks, &a.ImageID, &a.Image,
|
||
&a.Published, &a.PublishedAt, &a.SEOTitle, &a.SEODesc, &a.SEOH1); err != nil {
|
||
return nil, err
|
||
}
|
||
out = append(out, a)
|
||
}
|
||
return out, rows.Err()
|
||
}
|
||
|
||
type PostInput struct {
|
||
Kind string
|
||
Slug string
|
||
Title string
|
||
Lead string
|
||
Blocks json.RawMessage
|
||
ImageID *int64
|
||
Published bool
|
||
PublishedAt time.Time
|
||
SEOTitle string
|
||
SEODesc string
|
||
SEOH1 string
|
||
}
|
||
|
||
func (s *Storage) AdminCreatePost(ctx context.Context, in PostInput) (int64, error) {
|
||
var id int64
|
||
err := s.pool.QueryRow(ctx, `
|
||
INSERT INTO posts (kind, slug, title, lead, blocks, image_id, published, published_at,
|
||
seo_title, seo_description, seo_h1)
|
||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11)
|
||
RETURNING id`,
|
||
in.Kind, in.Slug, in.Title, in.Lead, in.Blocks, in.ImageID, in.Published, in.PublishedAt,
|
||
in.SEOTitle, in.SEODesc, in.SEOH1).Scan(&id)
|
||
return id, wrapConflict(err)
|
||
}
|
||
|
||
// AdminUpdatePost меняет публикацию; вид (новость или статья) после создания не меняется.
|
||
func (s *Storage) AdminUpdatePost(ctx context.Context, id int64, in PostInput) error {
|
||
tag, err := s.pool.Exec(ctx, `
|
||
UPDATE posts SET slug=$3, title=$4, lead=$5, blocks=$6, image_id=$7, published=$8,
|
||
published_at=$9, seo_title=$10, seo_description=$11, seo_h1=$12, updated_at=now()
|
||
WHERE id=$1 AND kind=$2 AND deleted_at IS NULL`,
|
||
id, in.Kind, in.Slug, in.Title, in.Lead, in.Blocks, in.ImageID, in.Published,
|
||
in.PublishedAt, in.SEOTitle, in.SEODesc, in.SEOH1)
|
||
if err != nil {
|
||
return wrapConflict(err)
|
||
}
|
||
if tag.RowsAffected() == 0 {
|
||
return ErrNotFound
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// AdminDeletePost — мягкое удаление, как у акций.
|
||
func (s *Storage) AdminDeletePost(ctx context.Context, kind string, id int64) error {
|
||
tag, err := s.pool.Exec(ctx, `
|
||
UPDATE posts SET deleted_at=now(), published=false, updated_at=now()
|
||
WHERE id=$1 AND kind=$2 AND deleted_at IS NULL`, id, kind)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if tag.RowsAffected() == 0 {
|
||
return ErrNotFound
|
||
}
|
||
return nil
|
||
}
|