Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Style (spam) bulk removal for moderators #272

Merged
merged 8 commits into from
Oct 18, 2023
Merged
88 changes: 49 additions & 39 deletions handlers/style/ban.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,54 @@ func BanGet(c *fiber.Ctx) error {
})
}

func BanStyle(style models.APIStyle, u *models.APIUser, user *storage.User, i int, id string, c *fiber.Ctx) (models.Log, error) {

event := models.Log{
UserID: u.ID,
Username: u.Username,
Kind: models.LogRemoveStyle,
TargetUserName: style.Username,
TargetData: style.Name,
Reason: strings.TrimSpace(c.FormValue("reason")),
Message: strings.TrimSpace(c.FormValue("message")),
Censor: c.FormValue("censor") == "on",
}

notification := models.Notification{
Kind: models.KindBannedStyle,
TargetID: int(event.ID),
UserID: int(user.ID),
StyleID: int(style.ID),
}

// INSERT INTO `logs`
err := database.Conn.Transaction(func(tx *gorm.DB) error {
if err := storage.DeleteUserstyle(tx, i); err != nil {
return err
}
if err := models.DeleteStats(tx, i); err != nil {
return err
}
if err := storage.DeleteSearchData(tx, i); err != nil {
return err
}
if err := models.CreateLog(tx, &event); err != nil {
return err
}
if err := models.CreateNotification(tx, &notification); err != nil {
return err
}
return models.RemoveStyleCode(id)
})
if err != nil {
return event, err
}

cache.Code.Remove(i)

return event, nil
}

func BanPost(c *fiber.Ctx) error {
u, _ := jwt.User(c)

Expand Down Expand Up @@ -85,43 +133,7 @@ func BanPost(c *fiber.Ctx) error {
})
}

event := models.Log{
UserID: u.ID,
Username: u.Username,
Kind: models.LogRemoveStyle,
TargetUserName: style.Username,
TargetData: style.Name,
Reason: strings.TrimSpace(c.FormValue("reason")),
Message: strings.TrimSpace(c.FormValue("message")),
Censor: c.FormValue("censor") == "on",
}

notification := models.Notification{
Kind: models.KindBannedStyle,
TargetID: int(event.ID),
UserID: int(user.ID),
StyleID: int(style.ID),
}

// INSERT INTO `logs`
err = database.Conn.Transaction(func(tx *gorm.DB) error {
if err = storage.DeleteUserstyle(tx, i); err != nil {
return err
}
if err = models.DeleteStats(tx, i); err != nil {
return err
}
if err = storage.DeleteSearchData(tx, i); err != nil {
return err
}
if err = models.CreateLog(tx, &event); err != nil {
return err
}
if err = models.CreateNotification(tx, &notification); err != nil {
return err
}
return models.RemoveStyleCode(id)
})
event, err := BanStyle(*style, u, user, i, id, c)
if err != nil {
log.Database.Printf("Failed to remove %d: %s\n", i, err)
return c.Render("err", fiber.Map{
Expand All @@ -130,8 +142,6 @@ func BanPost(c *fiber.Ctx) error {
})
}

cache.Code.Remove(i)

go sendRemovalEmail(user, style, event)

return c.Redirect("/modlog", fiber.StatusSeeOther)
Expand Down
148 changes: 148 additions & 0 deletions handlers/style/bulkban.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package style

import (
"strconv"
"strings"

"github.com/gofiber/fiber/v2"

"userstyles.world/handlers/jwt"
"userstyles.world/models"
"userstyles.world/modules/config"
"userstyles.world/modules/email"
"userstyles.world/modules/log"
"userstyles.world/modules/storage"
)

func BulkBanGet(c *fiber.Ctx) error {
u, _ := jwt.User(c)

// Check if logged-in user has permissions.
if !u.IsModOrAdmin() {
c.Status(fiber.StatusUnauthorized)
return c.Render("err", fiber.Map{
"Title": "You are not authorized to perform this action",
"User": u,
})
}

userid, err := c.ParamsInt("userid")
if err != nil || userid < 1 {
return c.Status(fiber.StatusBadRequest).Render("err", fiber.Map{
"User": u,
"Title": "Invalid user ID",
})
}

// fixme: this check is not working
user, _ := storage.FindUser(uint(userid))
if user == nil {
c.Status(fiber.StatusInternalServerError)
return c.Status(fiber.StatusBadRequest).Render("err", fiber.Map{
"User": u,
"Title": "Could not find such user",
})
}

return c.Render("style/bulkban", fiber.Map{
"Title": "Perform a bulk ban",
"User": u,
"UserID": userid,
})
}

func BulkBanPost(c *fiber.Ctx) error {
u, _ := jwt.User(c)

// Check if logged-in user has permissions.
if !u.IsModOrAdmin() {
c.Status(fiber.StatusUnauthorized)
return c.Render("err", fiber.Map{
"Title": "You are not authorized to perform this action",
"User": u,
})
}

userid, err := c.ParamsInt("userid")
if err != nil || userid < 1 {
return c.Status(fiber.StatusBadRequest).Render("err", fiber.Map{
"User": u,
"Title": "Invalid user ID",
})
}

// fixme: this check is not working
user, _ := storage.FindUser(uint(userid))
if user == nil {
c.Status(fiber.StatusInternalServerError)
return c.Status(fiber.StatusBadRequest).Render("err", fiber.Map{
"User": u,
"Title": "Could not find such user",
})
}

styles := []models.APIStyle{}
ids := strings.Split(c.FormValue("ids"), ",")

// Process all IDs for problems not to have any errors in between of removal
for _, element := range ids {
id := strings.TrimSpace(element)

style, err := models.GetStyleByID(id)
if err != nil {
c.Status(fiber.StatusNotFound)
return c.Render("err", fiber.Map{
"Title": "Operation failed",
"ErrTitle": "Style " + id + " was not found",
"User": u,
})
}
if int(style.UserID) != userid {
c.Status(fiber.StatusNotFound)
return c.Render("err", fiber.Map{
"Title": "Operation failed",
"ErrTitle": "User " + strconv.Itoa(int(style.UserID)) + " is not the author of style " + id,
"User": u,
})
}
styles = append(styles, *style)

_, err = strconv.Atoi(id)
if err != nil {
c.Status(fiber.StatusNotFound)
return c.Render("err", fiber.Map{
"Title": "Operation failed",
"ErrTitle": id + " is not a string",
"User": u,
})
}
}

// lastevent is used to link to the newest event in the modlog
// so the user will be presented with all of them on the screen.
var lastevent models.Log
for index, style := range styles {
event, _ := BanStyle(style, u, user, int(style.ID), strconv.Itoa(int(style.ID)), c)
if index == len(styles)-1 {
lastevent = event
}
}

go sendBulkRemovalEmail(user, styles, lastevent)

return c.Redirect("/modlog", fiber.StatusSeeOther)
}

func sendBulkRemovalEmail(user *storage.User, styles []models.APIStyle, firstentry models.Log) {
args := fiber.Map{
"User": user,
"Styles": styles,
"Log": firstentry,
"Link": config.BaseURL + "/modlog#id-" + strconv.Itoa(int(firstentry.ID)),
}

title := strconv.Itoa(len(styles)) + " of your style have been removed"
if err := email.Send("style/bulkban", user.Email, title, args); err != nil {
log.Warn.Printf("Failed to email %d: %s\n", user.ID, err)
}
}
2 changes: 2 additions & 0 deletions handlers/style/style.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ func Routes(app *fiber.App) {
r.Get("/styles/promote/:id", jwtware.Protected, Promote)
r.Get("/styles/ban/:id", jwtware.Protected, BanGet)
r.Post("/styles/ban/:id", jwtware.Protected, BanPost)
r.Get("/styles/bulk-ban/:userid", jwtware.Protected, BulkBanGet)
r.Post("/styles/bulk-ban/:userid", jwtware.Protected, BulkBanPost)
r.Static("/preview", config.PublicDir, fiber.Static{
MaxAge: 2678400, // 1 month
})
Expand Down
29 changes: 29 additions & 0 deletions web/views/email/style/bulkban.html.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{{ template "email/greeting.html" . }}

{{ template "email/noticeaction.html" . }}

<p>
Some of your styles have been removed from our platform for the following reason:<br>
{{ .Log.Reason }}
</p>

<p>
Styles that were removed:
<ul>
{{ range .Styles -}}
<li>
{{ .Name }}
</li>
{{ end }}
</ul>
vednoc marked this conversation as resolved.
Show resolved Hide resolved
</p>

{{ with .Log.Message -}}
<p>Additional message from the moderator:<br> {{ . }}</p>
{{ end }}

{{ template "email/actionrecorded.html" . }}

{{ template "email/getintouch.html" . }}

{{ template "email/regardsmod.html" . }}
19 changes: 19 additions & 0 deletions web/views/email/style/bulkban.text.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{{ template "email/greeting.text" . }}

{{ template "email/noticeaction.text" . }}

Some of your styles have been removed from our platform for the following reason:
{{ .Log.Reason }}

Styles that were removed:
{{ range .Styles -}}
- {{ .Name }}
{{ end }}

{{ with .Log.Message }} Additional message from the moderator: {{ . }}{{ end }}
astyled marked this conversation as resolved.
Show resolved Hide resolved

{{ template "email/actionrecorded.text" . }}

{{ template "email/getintouch.text" . }}

{{ template "email/regardsmod.text" . }}
43 changes: 43 additions & 0 deletions web/views/style/bulkban.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<section class="ta:c">
<h1>{{ .Title }}</h1>
<p>This action is irreversible.</p>
</section>

<style>
textarea[name="ids"] { min-height: 100px; }
</style>

<section class="limit">
<form class="form-wrapper" method="post" action="/styles/bulk-ban/{{ .UserID }}">
<label class="mb:m f:b">Enter IDs of styles to remove bellow</label>

<textarea
type="text" name="ids" maxlength="5000"
placeholder="9425,9709,12363"></textarea>

<label for="reason">Reason for ban</label>
<i class="fg:3">Be aware that this reason will be made public alongside this action.</i>
<input
required
type="text" name="reason"
placeholder="Your reason to ban these styles">

<label for="message">Private message for the author</label>
<i class="fg:3">For example, a hint about was done wrong and what can be done now. Will be included in the email.</i>
<textarea
type="text" name="message" maxlength="5000"
placeholder="Your message to the author of this style"></textarea>

<div class="checkbox flex">
<input type="checkbox" name="censor">
{{ template "partials/checkboxes" }}
<label class="ml:s" for="censor">Censor style's information</label>
</div>
<i class="fg:3">This will censor the information about the styles with a spoiler, use this if the style has an innapropiate name.</i>

<div class="mt:m">
<button class="btn primary mr:s" type="submit">Confirm</button>
<a class="fg:1" href="/style/{{ .Style.ID }}">Cancel</a>
</div>
</form>
</section>
3 changes: 2 additions & 1 deletion web/views/user/profile.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
</time>
</p>
{{ if ne .Profile.ID .User.ID }}
<a href="/user/ban/{{ .Profile.ID }}">Ban this user</a>
<p><a href="/user/ban/{{ .Profile.ID }}">Ban this user</a></p>
<p><a href="/styles/bulk-ban/{{ .Profile.ID }}">Style bulk-removal</a></p>
{{ end }}
{{ end }}
</section>
Expand Down