新增 admin.ask.read、GET /admin/ask/threads*(AskSessionView)与 admin-h5「问答」页;禁改消息/UGC/真支付。 Co-authored-by: Cursor <cursoragent@cursor.com>
105 lines
3.0 KiB
Go
105 lines
3.0 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5"
|
|
)
|
|
|
|
// AskSessionView is ops read meta for one ask thread.
|
|
type AskSessionView struct {
|
|
ID uuid.UUID `json:"id"`
|
|
UserID uuid.UUID `json:"user_id"`
|
|
ProfileID uuid.UUID `json:"profile_id"`
|
|
Scene *string `json:"scene,omitempty"`
|
|
MessageCount int `json:"message_count"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
UpdatedAt time.Time `json:"updated_at"`
|
|
}
|
|
|
|
// AskMessageView is a read-only message row for ops.
|
|
type AskMessageView struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Role string `json:"role"`
|
|
Content string `json:"content"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
}
|
|
|
|
// ListAskSessions returns recent ask threads (optional user filter).
|
|
func (r *AdminRepo) ListAskSessions(ctx context.Context, userID *uuid.UUID, limit, offset int) ([]AskSessionView, error) {
|
|
if limit <= 0 || limit > 100 {
|
|
limit = 20
|
|
}
|
|
if offset < 0 {
|
|
offset = 0
|
|
}
|
|
rows, err := r.Pool.Query(ctx, `
|
|
SELECT t.id, t.user_id, t.profile_id, t.scene, t.created_at, t.updated_at,
|
|
(SELECT count(*)::int FROM ask_messages m
|
|
WHERE m.thread_id=t.id AND m.deleted_at IS NULL) AS message_count
|
|
FROM ask_threads t
|
|
WHERE t.deleted_at IS NULL
|
|
AND ($1::uuid IS NULL OR t.user_id=$1)
|
|
ORDER BY t.updated_at DESC
|
|
LIMIT $2 OFFSET $3`, userID, limit, offset)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []AskSessionView
|
|
for rows.Next() {
|
|
var s AskSessionView
|
|
if err := rows.Scan(
|
|
&s.ID, &s.UserID, &s.ProfileID, &s.Scene, &s.CreatedAt, &s.UpdatedAt, &s.MessageCount,
|
|
); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, s)
|
|
}
|
|
return out, rows.Err()
|
|
}
|
|
|
|
// GetAskSession loads one thread meta or ErrNoRows.
|
|
func (r *AdminRepo) GetAskSession(ctx context.Context, threadID uuid.UUID) (*AskSessionView, error) {
|
|
var s AskSessionView
|
|
err := r.Pool.QueryRow(ctx, `
|
|
SELECT t.id, t.user_id, t.profile_id, t.scene, t.created_at, t.updated_at,
|
|
(SELECT count(*)::int FROM ask_messages m
|
|
WHERE m.thread_id=t.id AND m.deleted_at IS NULL) AS message_count
|
|
FROM ask_threads t
|
|
WHERE t.id=$1 AND t.deleted_at IS NULL`, threadID,
|
|
).Scan(&s.ID, &s.UserID, &s.ProfileID, &s.Scene, &s.CreatedAt, &s.UpdatedAt, &s.MessageCount)
|
|
if errors.Is(err, pgx.ErrNoRows) {
|
|
return nil, err
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &s, nil
|
|
}
|
|
|
|
// ListAskMessagesForAdmin returns messages oldest-first.
|
|
func (r *AdminRepo) ListAskMessagesForAdmin(ctx context.Context, threadID uuid.UUID) ([]AskMessageView, error) {
|
|
rows, err := r.Pool.Query(ctx, `
|
|
SELECT id, role, content, created_at
|
|
FROM ask_messages
|
|
WHERE thread_id=$1 AND deleted_at IS NULL
|
|
ORDER BY created_at ASC`, threadID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
var out []AskMessageView
|
|
for rows.Next() {
|
|
var m AskMessageView
|
|
if err := rows.Scan(&m.ID, &m.Role, &m.Content, &m.CreatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, m)
|
|
}
|
|
return out, rows.Err()
|
|
}
|