From 817c60c4e0aac390726987b22462c885ee7964ba Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Thu, 19 Sep 2024 21:02:21 +0800 Subject: [PATCH] :sparkles: Abuse report system --- pkg/internal/database/migrator.go | 1 + pkg/internal/models/reports.go | 19 ++++++++ pkg/internal/server/api/index.go | 11 +++++ pkg/internal/server/api/reports.go | 77 ++++++++++++++++++++++++++++++ pkg/internal/services/reports.go | 75 +++++++++++++++++++++++++++++ 5 files changed, 183 insertions(+) create mode 100644 pkg/internal/models/reports.go create mode 100644 pkg/internal/server/api/reports.go create mode 100644 pkg/internal/services/reports.go diff --git a/pkg/internal/database/migrator.go b/pkg/internal/database/migrator.go index a3545e9..dece2ef 100644 --- a/pkg/internal/database/migrator.go +++ b/pkg/internal/database/migrator.go @@ -27,6 +27,7 @@ var AutoMaintainRange = []any{ &models.ApiKey{}, &models.SignRecord{}, &models.PreferenceNotification{}, + &models.AbuseReport{}, } func RunMigration(source *gorm.DB) error { diff --git a/pkg/internal/models/reports.go b/pkg/internal/models/reports.go new file mode 100644 index 0000000..0b07ea1 --- /dev/null +++ b/pkg/internal/models/reports.go @@ -0,0 +1,19 @@ +package models + +const ( + ReportStatusPending = "pending" + ReportStatusReviewing = "reviewing" + ReportStatusConfirmed = "confirmed" + ReportStatusRejected = "rejected" + ReportStatusProcessed = "processed" +) + +type AbuseReport struct { + BaseModel + + Resource string `json:"resource"` + Reason string `json:"reason"` + Status string `json:"status"` + AccountID uint `json:"account_id"` + Account Account `json:"account"` +} diff --git a/pkg/internal/server/api/index.go b/pkg/internal/server/api/index.go index 53c61ab..9f58d2f 100644 --- a/pkg/internal/server/api/index.go +++ b/pkg/internal/server/api/index.go @@ -30,6 +30,17 @@ func MapAPIs(app *fiber.App, baseURL string) { preferences.Put("/notifications", updateNotificationPreference) } + reports := api.Group("/reports").Name("Reports API") + { + abuse := reports.Group("/abuse").Name("Abuse Reports") + { + abuse.Get("/", listAbuseReports) + abuse.Get("/:id", getAbuseReport) + abuse.Put("/:id/status", updateAbuseReportStatus) + abuse.Post("/", createAbuseReport) + } + } + api.Get("/users/lookup", lookupAccount) api.Get("/users/search", searchAccount) diff --git a/pkg/internal/server/api/reports.go b/pkg/internal/server/api/reports.go new file mode 100644 index 0000000..60deec6 --- /dev/null +++ b/pkg/internal/server/api/reports.go @@ -0,0 +1,77 @@ +package api + +import ( + "git.solsynth.dev/hydrogen/passport/pkg/internal/models" + "git.solsynth.dev/hydrogen/passport/pkg/internal/server/exts" + "git.solsynth.dev/hydrogen/passport/pkg/internal/services" + "github.com/gofiber/fiber/v2" +) + +func listAbuseReports(c *fiber.Ctx) error { + if err := exts.EnsureAuthenticated(c); err != nil { + return err + } + user := c.Locals("user").(models.Account) + + reports, err := services.ListAbuseReport(user) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + return c.JSON(reports) +} + +func getAbuseReport(c *fiber.Ctx) error { + id, _ := c.ParamsInt("id") + report, err := services.GetAbuseReport(uint(id)) + if err != nil { + return fiber.NewError(fiber.StatusNotFound, err.Error()) + } + + return c.JSON(report) +} + +func updateAbuseReportStatus(c *fiber.Ctx) error { + if err := exts.EnsureGrantedPerm(c, "DealAbuseReport", true); err != nil { + return err + } + + var data struct { + Status string `json:"status" validate:"required"` + } + + if err := exts.BindAndValidate(c, &data); err != nil { + return err + } + + id, _ := c.ParamsInt("id") + + if err := services.UpdateAbuseReportStatus(uint(id), data.Status); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + return c.SendStatus(fiber.StatusOK) +} + +func createAbuseReport(c *fiber.Ctx) error { + if err := exts.EnsureAuthenticated(c); err != nil { + return err + } + user := c.Locals("user").(models.Account) + + var data struct { + Resource string `json:"resource" validate:"required"` + Reason string `json:"reason" validate:"required,min=16,max=4096"` + } + + if err := exts.BindAndValidate(c, &data); err != nil { + return err + } + + report, err := services.NewAbuseReport(data.Resource, data.Reason, user) + if err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + + return c.JSON(report) +} diff --git a/pkg/internal/services/reports.go b/pkg/internal/services/reports.go new file mode 100644 index 0000000..2467711 --- /dev/null +++ b/pkg/internal/services/reports.go @@ -0,0 +1,75 @@ +package services + +import ( + "fmt" + + "git.solsynth.dev/hydrogen/passport/pkg/internal/database" + "git.solsynth.dev/hydrogen/passport/pkg/internal/models" +) + +func ListAbuseReport(account models.Account) ([]models.AbuseReport, error) { + var reports []models.AbuseReport + err := database.C. + Where("account_id = ?", account.ID). + Find(&reports).Error + return reports, err +} + +func GetAbuseReport(id uint) (models.AbuseReport, error) { + var report models.AbuseReport + err := database.C. + Where("id = ?", id). + First(&report).Error + return report, err +} + +func UpdateAbuseReportStatus(id uint, status string) error { + var report models.AbuseReport + err := database.C. + Where("id = ?", id). + Preload("Account"). + First(&report).Error + if err != nil { + return err + } + + report.Status = status + account := report.Account + + err = database.C.Save(&report).Error + if err != nil { + return err + } + + NewNotification(models.Notification{ + Topic: "reports.feedback", + Title: "Abuse report status has been changed.", + Body: fmt.Sprintf("The report created by you with ID #%d's status has been changed to %s", id, status), + Account: account, + AccountID: account.ID, + }) + + return nil +} + +func NewAbuseReport(resource string, reason string, account models.Account) (models.AbuseReport, error) { + var report models.AbuseReport + if err := database.C. + Where( + "resource = ? AND account_id = ? AND status IN ?", + resource, + account.ID, + []string{models.ReportStatusPending, models.ReportStatusReviewing}, + ).First(&report).Error; err == nil { + return report, fmt.Errorf("you already reported this resource and it still in process") + } + + report = models.AbuseReport{ + Resource: resource, + Reason: reason, + AccountID: account.ID, + } + + err := database.C.Create(&report).Error + return report, err +}