Files
digital-psychology/apps/api/internal/repository/cms_feed_slot_write.go
T
jackyu66gitandCursor 56f661748a feat(ECR-042): OpsCMS FeedSlot 薄写面
Admin POST/PUT 复用 admin.cms.write;C 端 GET /home/feed-slots;首页无 active 槽隐藏推荐区、失败回退展示;无新 migration。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 20:02:01 +08:00

144 lines
3.9 KiB
Go

package repository
import (
"context"
"encoding/json"
"errors"
"strings"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
)
// FeedSlotWriteInput is create/update payload.
type FeedSlotWriteInput struct {
Code string
Title string
SlotKey string
Placement string
Active bool
}
// ListActiveFeedSlots returns active slots for placement.
func (r *AdminRepo) ListActiveFeedSlots(ctx context.Context, placement string) ([]FeedSlotRow, error) {
rows, err := r.Pool.Query(ctx, `
SELECT id, code, title, slot_key, placement, active, system, updated_at
FROM ops_feed_slots
WHERE active = true AND placement = $1
ORDER BY code ASC
LIMIT 100`, placement)
if err != nil {
return nil, err
}
defer rows.Close()
var out []FeedSlotRow
for rows.Next() {
var s FeedSlotRow
if err := rows.Scan(
&s.ID, &s.Code, &s.Title, &s.SlotKey, &s.Placement, &s.Active, &s.System, &s.UpdatedAt,
); err != nil {
return nil, err
}
out = append(out, s)
}
return out, rows.Err()
}
// CreateFeedSlotWithAudit inserts and audits.
func (r *AdminRepo) CreateFeedSlotWithAudit(
ctx context.Context, adminID uuid.UUID, in FeedSlotWriteInput, meta json.RawMessage,
) (*FeedSlotRow, error) {
tx, err := r.Pool.Begin(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback(ctx)
var s FeedSlotRow
err = tx.QueryRow(ctx, `
INSERT INTO ops_feed_slots(code, title, slot_key, placement, active, system)
VALUES ($1,$2,$3,$4,$5,false)
RETURNING id, code, title, slot_key, placement, active, system, updated_at`,
in.Code, in.Title, in.SlotKey, in.Placement, in.Active,
).Scan(&s.ID, &s.Code, &s.Title, &s.SlotKey, &s.Placement, &s.Active, &s.System, &s.UpdatedAt)
if err != nil {
return nil, mapFeedSlotWriteErr(err)
}
if meta == nil {
meta = json.RawMessage(`{}`)
}
if _, err := tx.Exec(ctx, `
INSERT INTO admin_audit_logs(admin_id, action, target_type, target_id, meta)
VALUES ($1,'cms.feed_slot.create','feed_slot',$2,$3)`,
adminID, s.ID.String(), meta,
); err != nil {
return nil, err
}
if err := tx.Commit(ctx); err != nil {
return nil, err
}
return &s, nil
}
// UpdateFeedSlotWithAudit updates and audits.
func (r *AdminRepo) UpdateFeedSlotWithAudit(
ctx context.Context, adminID, id uuid.UUID, in FeedSlotWriteInput, meta json.RawMessage,
) (*FeedSlotRow, error) {
tx, err := r.Pool.Begin(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback(ctx)
var system bool
var oldCode string
err = tx.QueryRow(ctx, `SELECT system, code FROM ops_feed_slots WHERE id=$1`, id).Scan(&system, &oldCode)
if errors.Is(err, pgx.ErrNoRows) {
return nil, pgx.ErrNoRows
}
if err != nil {
return nil, err
}
code := in.Code
if system {
code = oldCode
}
var s FeedSlotRow
err = tx.QueryRow(ctx, `
UPDATE ops_feed_slots
SET code=$2, title=$3, slot_key=$4, placement=$5, active=$6, updated_at=now()
WHERE id=$1
RETURNING id, code, title, slot_key, placement, active, system, updated_at`,
id, code, in.Title, in.SlotKey, in.Placement, in.Active,
).Scan(&s.ID, &s.Code, &s.Title, &s.SlotKey, &s.Placement, &s.Active, &s.System, &s.UpdatedAt)
if err != nil {
return nil, mapFeedSlotWriteErr(err)
}
if meta == nil {
meta = json.RawMessage(`{}`)
}
if _, err := tx.Exec(ctx, `
INSERT INTO admin_audit_logs(admin_id, action, target_type, target_id, meta)
VALUES ($1,'cms.feed_slot.update','feed_slot',$2,$3)`,
adminID, id.String(), meta,
); err != nil {
return nil, err
}
if err := tx.Commit(ctx); err != nil {
return nil, err
}
return &s, nil
}
func mapFeedSlotWriteErr(err error) error {
var pgErr *pgconn.PgError
if errors.As(err, &pgErr) && pgErr.Code == "23505" {
return errString("feed slot code conflict")
}
return err
}
// FeedSlotCodeConflict reports unique violation.
func FeedSlotCodeConflict(err error) bool {
return err != nil && strings.Contains(err.Error(), "feed slot code conflict")
}