// Copyright 2026 tamakyi. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. package question import ( "fmt" "github.com/sirupsen/logrus" "github.com/tamakyi/TamaBox/internal/context" "github.com/tamakyi/TamaBox/internal/db" "github.com/tamakyi/TamaBox/internal/form" "github.com/tamakyi/TamaBox/internal/i18n" "github.com/tamakyi/TamaBox/internal/shared" ) // Report 接收访客对单条已公开回答的举报。 // // 设计取舍(都是有意为之,改动前请先想清楚): // - **不要求登录**:这类内容的主要目击者是匿名访客,要求登录等于把举报率降到零; // - 理由只接受固定枚举,不做自由文本 —— 否则举报框本身成了新的辱骂输入口; // - 同 IP 同内容重复举报**幂等成功**,且提示与首次完全一致, // 否则「已举报过」会变成「这条内容是否存在」的探测接口; // - 举报只是信号:处置仍由箱主(后台删除/设私密)或运维(TamaBox restrict)完成, // 本 handler 不改变任何内容可见性。 func Report(ctx context.Context, pageUser *db.User, question *db.Question, f form.ReportQuestion) { back := fmt.Sprintf("/_/%s/%d", pageUser.Domain, question.ID) if ctx.HasError() || !db.IsValidReportReason(f.Reason) { ctx.SetErrorFlash(i18n.T(ctx.Lang(), "report.errInvalidReason")) ctx.Redirect(back) return } // 兜底:只有公开且已回答的内容可被举报(Questioner 中间件已挡住私密/未回答)。 if question.Answer == "" || question.IsPrivate { ctx.Redirect(back) return } ip := shared.ResolveClientIP(ctx.Request().Request) created, err := db.Reports.Create(ctx.Request().Context(), db.CreateReportOptions{ QuestionID: question.ID, ReporterIP: ip, Reason: f.Reason, }) if err != nil { logrus.WithContext(ctx.Request().Context()).WithError(err).Error("Failed to create content report") ctx.SetErrorFlash(i18n.T(ctx.Lang(), "report.errFailed")) ctx.Redirect(back) return } if created { logrus.WithContext(ctx.Request().Context()).WithFields(logrus.Fields{ "question_id": question.ID, "user_id": pageUser.ID, "reason": f.Reason, }).Warn("Content reported") notifyAbuseIfNeeded(pageUser) } // 首次与重复使用同一句提示(见上方设计取舍)。 ctx.SetSuccessFlash(i18n.T(ctx.Lang(), "report.flash.received")) ctx.Redirect(back) }