2025-02-17 14:19:47 +00:00
// Copyright 2025 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: GPL-3.0-or-later
package moderation
import (
"context"
2025-03-03 09:52:31 +00:00
"errors"
"slices"
2025-02-17 14:19:47 +00:00
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"xorm.io/builder"
)
// ReportStatusType defines the statuses a report (of abusive content) can have.
type ReportStatusType int //revive:disable-line:exported
const (
2025-02-22 11:31:31 +00:00
// ReportStatusTypeOpen represents the status of open reports that were not yet handled in any way.
ReportStatusTypeOpen ReportStatusType = iota + 1 // 1
// ReportStatusTypeHandled represents the status of valid reports, that have been acted upon.
2025-02-17 14:19:47 +00:00
ReportStatusTypeHandled // 2
2025-02-22 11:31:31 +00:00
// ReportStatusTypeIgnored represents the status of ignored reports, that were closed without any action.
ReportStatusTypeIgnored // 3
)
2025-02-28 10:41:00 +00:00
type (
// AbuseCategoryType defines the categories in which a user can include the reported content.
AbuseCategoryType int //revive:disable-line:exported
// AbuseCategoryItem defines a pair of value and it's corresponding translation key
// (used when new reports are submitted).
AbuseCategoryItem struct {
Value AbuseCategoryType
TranslationKey string
}
)
2025-02-22 11:31:31 +00:00
const (
AbuseCategoryTypeSpam AbuseCategoryType = iota + 1 // 1
AbuseCategoryTypeMalware // 2
AbuseCategoryTypeIllegalContent // 3
AbuseCategoryTypeOtherViolations // 4 (Other violations of platform rules)
2025-02-17 14:19:47 +00:00
)
2025-02-28 10:41:00 +00:00
// GetAbuseCategoriesList returns a list of pairs with the available abuse category types
// and their corresponding translation keys
func GetAbuseCategoriesList ( ) [ ] AbuseCategoryItem {
return [ ] AbuseCategoryItem {
{ AbuseCategoryTypeSpam , "moderation.abuse_category.spam" } ,
{ AbuseCategoryTypeMalware , "moderation.abuse_category.malware" } ,
{ AbuseCategoryTypeIllegalContent , "moderation.abuse_category.illegal_content" } ,
{ AbuseCategoryTypeOtherViolations , "moderation.abuse_category.other_violations" } ,
}
}
2025-02-17 14:19:47 +00:00
// ReportedContentType defines the types of content that can be reported
// (i.e. user/organization profile, repository, issue/pull, comment).
type ReportedContentType int //revive:disable-line:exported
const (
// ReportedContentTypeUser should be used when reporting abusive users or organizations.
ReportedContentTypeUser ReportedContentType = iota + 1 // 1
// ReportedContentTypeRepository should be used when reporting a repository with abusive content.
ReportedContentTypeRepository // 2
// ReportedContentTypeIssue should be used when reporting an issue or pull request with abusive content.
ReportedContentTypeIssue // 3
// ReportedContentTypeComment should be used when reporting a comment with abusive content.
ReportedContentTypeComment // 4
)
2025-03-03 09:52:31 +00:00
var allReportedContentTypes = [ ] ReportedContentType {
ReportedContentTypeUser ,
ReportedContentTypeRepository ,
ReportedContentTypeIssue ,
ReportedContentTypeComment ,
}
func ( t ReportedContentType ) IsValid ( ) bool {
return slices . Contains ( allReportedContentTypes , t )
}
2025-02-17 14:19:47 +00:00
// AbuseReport represents a report of abusive content.
type AbuseReport struct {
ID int64 ` xorm:"pk autoincr" `
Status ReportStatusType ` xorm:"NOT NULL DEFAULT 1" `
// The ID of the user who submitted the report.
ReporterID int64 ` xorm:"NOT NULL" ` // index ?!
// Reported content type: user/organization profile, repository, issue/pull or comment.
2025-02-22 11:31:31 +00:00
ContentType ReportedContentType ` xorm:"INDEX NOT NULL" `
2025-02-17 14:19:47 +00:00
// The ID of the reported item (based on ContentType: user, repository, issue or comment).
ContentID int64 ` xorm:"NOT NULL" `
2025-02-22 11:31:31 +00:00
// The abuse category selected by the reporter.
Category AbuseCategoryType ` xorm:"INDEX NOT NULL" `
2025-02-17 14:19:47 +00:00
// Remarks provided by the reporter.
2025-02-22 11:31:31 +00:00
Remarks string // TODO: ReporterReparks or Reason
2025-02-17 14:19:47 +00:00
// The ID of the corresponding shadow-copied content when exists; otherwise null.
2025-02-22 11:31:31 +00:00
ShadowCopyID * int64 ` xorm:"DEFAULT NULL" `
CreatedUnix timeutil . TimeStamp ` xorm:"created NOT NULL" `
2025-02-17 14:19:47 +00:00
}
func init ( ) {
// RegisterModel will create the table if does not already exist
// or any missing columns if the table was previously created.
// It will not drop or rename existing columns (when struct has changed).
db . RegisterModel ( new ( AbuseReport ) )
}
2025-03-03 09:52:31 +00:00
// IsReported reports whether one or more reports were already submitted for contentType and contentID
// (regardless the status of the reports).
2025-02-17 14:19:47 +00:00
func IsReported ( ctx context . Context , contentType ReportedContentType , contentID int64 ) bool {
// TODO: only consider the reports with 'New' status (and adjust the function name)?!
reported , _ := db . GetEngine ( ctx ) . Exist ( & AbuseReport { ContentType : contentType , ContentID : contentID } )
return reported
}
2025-03-03 09:52:31 +00:00
// AlreadyReportedByAndOpen returns if doerID has already submitted a report for contentType and contentID that is still Open.
func AlreadyReportedByAndOpen ( ctx context . Context , doerID int64 , contentType ReportedContentType , contentID int64 ) bool {
reported , _ := db . GetEngine ( ctx ) . Exist ( & AbuseReport {
Status : ReportStatusTypeOpen ,
ReporterID : doerID ,
ContentType : contentType ,
ContentID : contentID ,
} )
2025-02-17 14:19:47 +00:00
return reported
}
2025-02-28 10:41:00 +00:00
func ReportAbuse ( ctx context . Context , report * AbuseReport ) error {
if report . ContentType == ReportedContentTypeUser && report . ReporterID == report . ContentID {
2025-03-03 09:52:31 +00:00
return errors . New ( "reporting yourself is not allowed" )
2025-02-28 10:41:00 +00:00
}
2025-03-03 09:52:31 +00:00
if AlreadyReportedByAndOpen ( ctx , report . ReporterID , report . ContentType , report . ContentID ) {
2025-02-17 14:19:47 +00:00
log . Warn ( "Seems that user %d wanted to report again the content with type %d and ID %d; this request will be ignored." , report . ReporterID , report . ContentType , report . ContentID )
return nil
}
2025-02-22 11:31:31 +00:00
report . Status = ReportStatusTypeOpen
2025-02-17 14:19:47 +00:00
_ , err := db . GetEngine ( ctx ) . Insert ( report )
return err
}
// MarkAsHandled will change the status to 'Handled' for all reports linked to the same item (user, repository, issue or comment).
func MarkAsHandled ( ctx context . Context , contentType ReportedContentType , contentID int64 ) error {
2025-02-22 11:31:31 +00:00
return updateStatus ( ctx , contentType , contentID , ReportStatusTypeHandled )
}
// MarkAsIgnored will change the status to 'Ignored' for all reports linked to the same item (user, repository, issue or comment).
func MarkAsIgnored ( ctx context . Context , contentType ReportedContentType , contentID int64 ) error {
return updateStatus ( ctx , contentType , contentID , ReportStatusTypeIgnored )
}
// updateStatus will set the provided status for any reports linked to the item with the given type and ID.
func updateStatus ( ctx context . Context , contentType ReportedContentType , contentID int64 , status ReportStatusType ) error {
2025-02-17 14:19:47 +00:00
_ , err := db . GetEngine ( ctx ) . Where ( builder . Eq {
"content_type" : contentType ,
"content_id" : contentID ,
2025-02-22 11:31:31 +00:00
} ) . Cols ( "status" ) . Update ( & AbuseReport { Status : status } )
2025-02-17 14:19:47 +00:00
return err
}