chore: update admin apis
This commit is contained in:
@@ -19,6 +19,11 @@ type AdminUser struct {
|
|||||||
BannedAt *time.Time `json:"banned_at"`
|
BannedAt *time.Time `json:"banned_at"`
|
||||||
PostCount int `json:"post_count"`
|
PostCount int `json:"post_count"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
|
||||||
|
// Connected auth methods. HasPassword means email+password login is set up;
|
||||||
|
// Providers lists linked OAuth providers (e.g. "google", "facebook").
|
||||||
|
HasPassword bool `json:"has_password"`
|
||||||
|
Providers []string `json:"providers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminPost is a row in the admin posts table.
|
// AdminPost is a row in the admin posts table.
|
||||||
|
|||||||
@@ -37,6 +37,9 @@ func (r *AdminRepo) ListUsers(ctx context.Context, q string, limit, offset int)
|
|||||||
u.email_verified_at, u.banned_at,
|
u.email_verified_at, u.banned_at,
|
||||||
(SELECT COUNT(*) FROM posts p WHERE p.user_id = u.id AND p.deleted_at IS NULL) AS post_count,
|
(SELECT COUNT(*) FROM posts p WHERE p.user_id = u.id AND p.deleted_at IS NULL) AS post_count,
|
||||||
u.created_at,
|
u.created_at,
|
||||||
|
(u.password_hash IS NOT NULL) AS has_password,
|
||||||
|
(SELECT COALESCE(array_agg(oa.provider ORDER BY oa.provider), ARRAY[]::text[])
|
||||||
|
FROM oauth_accounts oa WHERE oa.user_id = u.id) AS providers,
|
||||||
COUNT(*) OVER() AS total
|
COUNT(*) OVER() AS total
|
||||||
FROM users u
|
FROM users u
|
||||||
WHERE ($1 = '' OR u.name ILIKE '%'||$1||'%' OR u.handle ILIKE '%'||$1||'%'
|
WHERE ($1 = '' OR u.name ILIKE '%'||$1||'%' OR u.handle ILIKE '%'||$1||'%'
|
||||||
@@ -53,7 +56,8 @@ func (r *AdminRepo) ListUsers(ctx context.Context, q string, limit, offset int)
|
|||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var u model.AdminUser
|
var u model.AdminUser
|
||||||
if err := rows.Scan(&u.ID, &u.Email, &u.Handle, &u.Name, &u.AvatarURL,
|
if err := rows.Scan(&u.ID, &u.Email, &u.Handle, &u.Name, &u.AvatarURL,
|
||||||
&u.Role, &u.Plan, &u.EmailVerifiedAt, &u.BannedAt, &u.PostCount, &u.CreatedAt, &total); err != nil {
|
&u.Role, &u.Plan, &u.EmailVerifiedAt, &u.BannedAt, &u.PostCount, &u.CreatedAt,
|
||||||
|
&u.HasPassword, &u.Providers, &total); err != nil {
|
||||||
return nil, 0, fmt.Errorf("scan admin user: %w", err)
|
return nil, 0, fmt.Errorf("scan admin user: %w", err)
|
||||||
}
|
}
|
||||||
out = append(out, u)
|
out = append(out, u)
|
||||||
@@ -178,8 +182,9 @@ func (r *AdminRepo) ListFeedback(ctx context.Context, status string, limit, offs
|
|||||||
return out, total, rows.Err()
|
return out, total, rows.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveReply stores an admin reply on a feedback row, stamps replied_at, and
|
// SaveReply stores an admin reply on a feedback row and stamps replied_at. It
|
||||||
// resolves it (if not already). It returns the recipient's email (the
|
// does NOT change resolved_at — replying and resolving are independent actions
|
||||||
|
// (resolve via ResolveFeedback). It returns the recipient's email (the
|
||||||
// submitter's account email, else the anonymous contact_email — may be nil if
|
// submitter's account email, else the anonymous contact_email — may be nil if
|
||||||
// neither is known) and name so the caller can send the reply email. ok is
|
// neither is known) and name so the caller can send the reply email. ok is
|
||||||
// false when the id doesn't exist.
|
// false when the id doesn't exist.
|
||||||
@@ -187,8 +192,7 @@ func (r *AdminRepo) SaveReply(ctx context.Context, id, message string) (email *s
|
|||||||
err = r.pg.Pool.QueryRow(ctx, `
|
err = r.pg.Pool.QueryRow(ctx, `
|
||||||
UPDATE feedback f
|
UPDATE feedback f
|
||||||
SET reply_message = $2,
|
SET reply_message = $2,
|
||||||
replied_at = now(),
|
replied_at = now()
|
||||||
resolved_at = COALESCE(resolved_at, now())
|
|
||||||
WHERE f.id = $1
|
WHERE f.id = $1
|
||||||
RETURNING COALESCE((SELECT email FROM users WHERE id = f.user_id), f.contact_email),
|
RETURNING COALESCE((SELECT email FROM users WHERE id = f.user_id), f.contact_email),
|
||||||
COALESCE((SELECT name FROM users WHERE id = f.user_id), '')`,
|
COALESCE((SELECT name FROM users WHERE id = f.user_id), '')`,
|
||||||
|
|||||||
@@ -88,9 +88,10 @@ func (s *AdminService) NotifyUser(ctx context.Context, userID, title, body strin
|
|||||||
return s.notif.NotifyAdminMessage(ctx, userID, title, body)
|
return s.notif.NotifyAdminMessage(ctx, userID, title, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplyFeedback stores the reply (resolving the row) and emails it to the
|
// ReplyFeedback stores the reply and emails it to the submitter when an
|
||||||
// submitter when an address is known. Returns found=false if the id is
|
// address is known. It does not resolve the row (resolve is a separate
|
||||||
// unknown, and emailed=true when a reply email was actually sent.
|
// action). Returns found=false if the id is unknown, and emailed=true when a
|
||||||
|
// reply email was actually sent.
|
||||||
func (s *AdminService) ReplyFeedback(ctx context.Context, id, message string) (found, emailed bool, err error) {
|
func (s *AdminService) ReplyFeedback(ctx context.Context, id, message string) (found, emailed bool, err error) {
|
||||||
email, name, ok, err := s.repo.SaveReply(ctx, id, message)
|
email, name, ok, err := s.repo.SaveReply(ctx, id, message)
|
||||||
if err != nil || !ok {
|
if err != nil || !ok {
|
||||||
|
|||||||
Reference in New Issue
Block a user