fix: fix ui reports
This commit is contained in:
@@ -208,7 +208,12 @@ func (h *AdminHandler) ResolveReport(w http.ResponseWriter, r *http.Request) {
|
||||
response.Error(w, http.StatusBadRequest, "type must be post, comment, or user")
|
||||
return
|
||||
}
|
||||
resolved, err := h.svc.ResolveReport(r.Context(), reportType, id)
|
||||
var in struct {
|
||||
Note string `json:"note"`
|
||||
}
|
||||
// Body is optional/best-effort; an empty/missing note resolves with no note.
|
||||
_ = json.NewDecoder(r.Body).Decode(&in)
|
||||
resolved, err := h.svc.ResolveReport(r.Context(), reportType, id, strings.TrimSpace(in.Note))
|
||||
if err != nil {
|
||||
response.Error(w, http.StatusInternalServerError, "could not resolve report")
|
||||
return
|
||||
|
||||
@@ -84,6 +84,13 @@ type AdminReport struct {
|
||||
ReporterHandle *string `json:"reporter_handle"`
|
||||
ReporterName *string `json:"reporter_name"`
|
||||
|
||||
// ResolutionNote is the admin's note left when resolving (nil while open).
|
||||
ResolutionNote *string `json:"resolution_note"`
|
||||
// TargetRemoved is true when the reported content is currently removed —
|
||||
// a soft-deleted post/comment, or a banned account — so the UI can offer
|
||||
// "Undo" instead of "Delete"/"Ban".
|
||||
TargetRemoved bool `json:"target_removed"`
|
||||
|
||||
// Target of the report. TargetID is the post/comment/user id; TargetText
|
||||
// is the post caption, comment body, or reported user's handle so the
|
||||
// admin sees what was flagged without a second fetch. TargetAuthorHandle
|
||||
|
||||
@@ -219,6 +219,7 @@ func (r *AdminRepo) ListReports(ctx context.Context, reportType, q string, limit
|
||||
SELECT r.id, r.category, r.reason, r.created_at, r.resolved_at,
|
||||
ru.handle, ru.name,
|
||||
r.post_id, p.caption, pu.handle,
|
||||
r.resolution_note, COALESCE(p.deleted_at IS NOT NULL, false),
|
||||
COUNT(*) OVER() AS total
|
||||
FROM post_reports r
|
||||
LEFT JOIN users ru ON ru.id = r.reporter_id
|
||||
@@ -234,6 +235,7 @@ func (r *AdminRepo) ListReports(ctx context.Context, reportType, q string, limit
|
||||
SELECT r.id, r.category, r.reason, r.created_at, r.resolved_at,
|
||||
ru.handle, ru.name,
|
||||
r.comment_id, c.body, cu.handle,
|
||||
r.resolution_note, COALESCE(c.deleted_at IS NOT NULL, false),
|
||||
COUNT(*) OVER() AS total
|
||||
FROM comment_reports r
|
||||
LEFT JOIN users ru ON ru.id = r.reporter_id
|
||||
@@ -248,8 +250,9 @@ func (r *AdminRepo) ListReports(ctx context.Context, reportType, q string, limit
|
||||
sql = `
|
||||
SELECT r.id, r.category, r.reason, r.created_at, r.resolved_at,
|
||||
ru.handle, ru.name,
|
||||
r.reported_id, tu.handle, tu.handle
|
||||
, COUNT(*) OVER() AS total
|
||||
r.reported_id, tu.handle, tu.handle,
|
||||
r.resolution_note, COALESCE(tu.banned_at IS NOT NULL, false),
|
||||
COUNT(*) OVER() AS total
|
||||
FROM user_reports r
|
||||
LEFT JOIN users ru ON ru.id = r.reporter_id
|
||||
LEFT JOIN users tu ON tu.id = r.reported_id
|
||||
@@ -274,7 +277,8 @@ func (r *AdminRepo) ListReports(ctx context.Context, reportType, q string, limit
|
||||
rep := model.AdminReport{Type: reportType}
|
||||
if err := rows.Scan(&rep.ID, &rep.Category, &rep.Reason, &rep.CreatedAt, &rep.ResolvedAt,
|
||||
&rep.ReporterHandle, &rep.ReporterName,
|
||||
&rep.TargetID, &rep.TargetText, &rep.TargetAuthorHandle, &total); err != nil {
|
||||
&rep.TargetID, &rep.TargetText, &rep.TargetAuthorHandle,
|
||||
&rep.ResolutionNote, &rep.TargetRemoved, &total); err != nil {
|
||||
return nil, 0, fmt.Errorf("scan admin report: %w", err)
|
||||
}
|
||||
out = append(out, rep)
|
||||
@@ -282,10 +286,10 @@ func (r *AdminRepo) ListReports(ctx context.Context, reportType, q string, limit
|
||||
return out, total, rows.Err()
|
||||
}
|
||||
|
||||
// ResolveReport stamps resolved_at=now() on a still-open report of the given
|
||||
// type. Idempotent and one-way (the WHERE guard). Returns true when this call
|
||||
// is the one that resolved it.
|
||||
func (r *AdminRepo) ResolveReport(ctx context.Context, reportType, id string) (bool, error) {
|
||||
// ResolveReport stamps resolved_at=now() and stores the admin's note on a
|
||||
// still-open report of the given type. Idempotent and one-way (the WHERE
|
||||
// guard). Returns true when this call is the one that resolved it.
|
||||
func (r *AdminRepo) ResolveReport(ctx context.Context, reportType, id, note string) (bool, error) {
|
||||
var table string
|
||||
switch reportType {
|
||||
case "post":
|
||||
@@ -299,8 +303,8 @@ func (r *AdminRepo) ResolveReport(ctx context.Context, reportType, id string) (b
|
||||
}
|
||||
// table is from a fixed whitelist above — safe to interpolate.
|
||||
tag, err := r.pg.Pool.Exec(ctx,
|
||||
fmt.Sprintf(`UPDATE %s SET resolved_at = now() WHERE id = $1 AND resolved_at IS NULL`, table),
|
||||
id,
|
||||
fmt.Sprintf(`UPDATE %s SET resolved_at = now(), resolution_note = $2 WHERE id = $1 AND resolved_at IS NULL`, table),
|
||||
id, note,
|
||||
)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("admin resolve %s report: %w", reportType, err)
|
||||
|
||||
@@ -121,8 +121,8 @@ func (s *AdminService) ReplyFeedback(ctx context.Context, id, message string) (f
|
||||
// ResolveReport stamps a report resolved. found=true when this call resolved a
|
||||
// previously-open report (idempotent: a no-op on an already-resolved/unknown id
|
||||
// returns false).
|
||||
func (s *AdminService) ResolveReport(ctx context.Context, reportType, id string) (bool, error) {
|
||||
return s.repo.ResolveReport(ctx, reportType, id)
|
||||
func (s *AdminService) ResolveReport(ctx context.Context, reportType, id, note string) (bool, error) {
|
||||
return s.repo.ResolveReport(ctx, reportType, id, note)
|
||||
}
|
||||
|
||||
// ─── Moderation actions ──────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user