Improve logging code #7

Merged
fnetX merged 7 commits from :add-logging into main 4 months ago
  1. 156
      backend/internal/log/log.go
  2. 15
      backend/internal/mailer/mailer.go
  3. 52
      backend/internal/util/errors.go
  4. 47
      backend/main.go
  5. 3
      backend/moderation_backend.ini.example
  6. 135
      backend/repos.go
  7. 156
      backend/users.go

@ -0,0 +1,156 @@
package log
import (
"errors"
"fmt"
"io"
stdLog "log"
"os"
"runtime"
"strings"
"time"
"github.com/go-ini/ini"
)
var (
// logger is the variable to which the code can write its output.
logger *stdLog.Logger
// The current absolute path to backend/ is stored in filePrefix; this is
// removed from the filename because the logging doesn't require any of the other junk.
filePrefix string
)
func init() {
// Make sure that logger is always initalized, this is to still be able to use the logger
// even when logger isn't yet initilazed with the correct values.
initializeLogger(os.Stdout)
// A quick and dirty way to get the backend/ absolute folder
_, filename, _, _ := runtime.Caller(0)
filePrefix = strings.TrimSuffix(filename, "backend/internal/log/log.go")
if filePrefix == filename {
// in case the source code file is moved, we can not trim the suffix, the code above should also be updated.
panic("unable to detect correct package prefix, please update file: " + filename)
}
}
// initializeLogger takes a input of multiple writers, which to all writers will be logged to.
func initializeLogger(w ...io.Writer) {
logger = stdLog.New(io.MultiWriter(w...), "", 0)
}
// Config takes the ini config and correctly adjust the logger variable.
func Config(cfg *ini.File) {
loggingSection := cfg.Section("logging")
outputLog := loggingSection.Key("output").String()
writers := []io.Writer{}
if strings.Contains(outputLog, "console") {
writers = append(writers, os.Stdout)
}
if strings.Contains(outputLog, "file") {
outputFile := loggingSection.Key("file").MustString("../debug.log")
f, err := os.OpenFile(outputFile, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o644)
if err != nil {
Fatal("Cannot open log output's file %q: %v", outputFile, err)
}
writers = append(writers, f)
}
initializeLogger(writers...)
}
// the Skipper struct can be passed as firt argument to adjust the skip offset.
type Skipper struct {
// SkipOffset will be added on top of the existing "2" skips.
SkipOffset int
}
func log(msg, prefix string, print bool, args ...interface{}) string {
caller := "?()"
skipOffset := 2
// Check if the first argument is Skipper and add the skipoffset accordingly.
if len(args) > 0 {
if skip, ok := args[0].(Skipper); ok {
skipOffset += skip.SkipOffset
args = args[1:]
}
}
pc, filename, line, ok := runtime.Caller(skipOffset)
if ok {
// Get caller function name.
fn := runtime.FuncForPC(pc)
if fn != nil {
caller = fn.Name() + "()"
// Remove prefix of binary's name.
lastIndex := strings.LastIndexByte(caller, '.')
if lastIndex > 0 && len(caller) > lastIndex+1 {
caller = caller[lastIndex+1:]
}
}
}
filename = strings.TrimPrefix(filename, filePrefix)
// Don't output long file names.
if len(filename) > 40 {
filename = "..." + filename[len(filename)-40:]
}
now := time.Now()
year, month, day := now.Date()
hour, min, sec := now.Clock()
// Output message:
// DATE TIME FILENAME:LINE:CALLER PREFIX: MSG
prefixedMessage := fmt.Sprintf("%d/%02d/%02d %02d:%02d:%02d %s:%d:%s %s: %s", year, month, day, hour, min, sec, filename, line, caller, prefix, msg)
// Only print the message if it has been requested.
if print {
if len(args) > 0 {
logger.Printf(prefixedMessage+"\n", args...)
} else {
logger.Println(prefixedMessage)
}
return ""
} else {
return fmt.Sprintf(prefixedMessage, args...)
}
}
// Info logs a message with the INFO prefix.
func Info(msg string, args ...interface{}) {
log(msg, "INFO", true, args...)
}
// Warn logs a message with the WARN prefix.
func Warn(msg string, args ...interface{}) {
log(msg, "WARN", true, args...)
}
// Error logs a message with the ERROR prefix.
func Error(msg string, args ...interface{}) {
log(msg, "ERROR", true, args...)
}
// Fatal logs a message with the FATAL prefix and then exit the program.
func Fatal(msg string, args ...interface{}) {
log(msg, "FATAL", true, args...)
os.Exit(1)
}
// NewError formats the given error into the logging style, but doesn't print it.
func NewError(msg string, args ...interface{}) error {
return errors.New(log(msg, "ERROR", false, args...))
}
// Println takes in a string and directly print it to the logger, thus not formatting it.
func Println(msg string) {
logger.Println(msg)
}

@ -17,7 +17,7 @@ var (
mailGiteaName string
)
func MailConfig(cfg *ini.File) {
func Config(cfg *ini.File) {
mailconfig := cfg.Section("mail")
hostname := mailconfig.Key("HOSTNAME").String()
port := mailconfig.Key("PORT").MustInt()
@ -35,8 +35,7 @@ func MailConfig(cfg *ini.File) {
func MailSend(toMail, toName, templateName string, templateData map[string]interface{}) error {
mailTemplate, err := template.ParseFiles("templates/mail/" + templateName + ".tmpl")
if err != nil {
fmt.Printf("Error reading mail template: %s", err.Error())
return err
return fmt.Errorf("ParseFiles[template=%s]: %v", templateName, err)
}
if toMail == "" {
@ -49,9 +48,8 @@ func MailSend(toMail, toName, templateName string, templateData map[string]inter
templateData["giteaName"] = mailGiteaName
var mailContent bytes.Buffer
if err = mailTemplate.Execute(&mailContent, templateData); err != nil {
fmt.Printf("Error parsing mail template: %s", err.Error())
return err
if err := mailTemplate.Execute(&mailContent, templateData); err != nil {
return fmt.Errorf("error parsing mail template: %v", err)
}
mail := gomail.NewMessage()
@ -62,9 +60,10 @@ func MailSend(toMail, toName, templateName string, templateData map[string]inter
}
mail.SetHeader("Subject", "Important notice regarding your content on Codeberg.org")
mail.SetBody("text/plain", mailContent.String())
if err = mailDialer.DialAndSend(mail); err != nil {
fmt.Printf("Error sending email: %s", err.Error())
return err
return fmt.Errorf("error sending email: %v", err)
}
return nil
}

@ -2,29 +2,41 @@ package util
import (
"encoding/json"
"fmt"
"net/http"
"strings"
"codeberg.org/codeberg/moderation/internal/log"
)
func ErrorCatcher(originName string, w http.ResponseWriter, r *http.Request, fn func(w http.ResponseWriter, r *http.Request) (err error, info string)) {
w.Header().Set("Content-Type", "text/json")
err, info := fn(w, r)
if err == nil {
return
}
func ErrorMiddleware(fn func(w http.ResponseWriter, r *http.Request) error) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
if err := fn(w, r); err != nil {
errorStr := err.Error()
// the function should return a error that already has been formatted.
log.Println(errorStr)
caller := "?"
// Split the error with spaces.
spacedSlice := strings.Split(errorStr, " ")
if len(spacedSlice) >= 3 {
// Get the third message, then split it on `:`
// Get the last(thus third) message which contains the caller.
caller = strings.Split(spacedSlice[2], ":")[2]
}
fmt.Printf("[%s]: (%s) %v\n", originName, info, err)
w.WriteHeader(500)
res, err := json.Marshal(struct {
Origin string
Details string
}{
Origin: originName,
Details: err.Error(),
})
if err != nil {
fmt.Printf("Error while encoding error: %v\n", err)
return
res, err := json.Marshal(struct {
Origin string
Details string
}{
Origin: caller,
Details: strings.Join(spacedSlice[2:], " "),
})
if err != nil {
log.Error("Error while encoding error JSON: %v", err)
return
}
_, _ = w.Write(res)
}
}
_, _ = w.Write([]byte(string(res)))
}

@ -17,6 +17,7 @@ import (
"github.com/go-chi/render"
"github.com/go-ini/ini"
"codeberg.org/codeberg/moderation/internal/log"
"codeberg.org/codeberg/moderation/internal/mailer"
)
@ -32,20 +33,19 @@ import (
* globals that are only accessed in a single file are defined there
* using a namespace alike filenameConstantname
*/
var giteaClient *gitea.Client
var (
jwtAuth *jwtauth.JWTAuth
giteaURL string
giteaClient *gitea.Client
jwtAuth *jwtauth.JWTAuth
giteaURL string
)
type PermissionLevel int
const (
PERMISSION_USER PermissionLevel = 1 // nolint:deadcode
PERMISSION_MEMBER PermissionLevel = 2 // nolint:deadcode
PERMISSION_MODERATOR PermissionLevel = 3
PERMISSION_ADMIN PermissionLevel = 4
PERMISSION_USER PermissionLevel = iota + 1 // nolint:deadcode
PERMISSION_MEMBER // nolint:deadcode
PERMISSION_MODERATOR
PERMISSION_ADMIN
)
type GenericRequest struct {
@ -61,50 +61,46 @@ type GenericError struct {
}
func (err *GenericError) Error() string {
return fmt.Sprintln(err.Action + ": " + err.Result)
return err.Action + ": " + err.Result
}
func main() {
// configuration
cfg, err := ini.Load("moderation_backend.ini")
if err != nil {
fmt.Printf("Failed to read file: %v\n", err)
os.Exit(1)
log.Fatal("ini.Load: %v", err)
}
appListenUrl := cfg.Section("moderation_app").Key("LISTEN_URL").String()
if appListenUrl == "" {
fmt.Printf("Please make sure you have a listen url in your moderation_backend.ini\n")
os.Exit(2)
log.Fatal("Please make sure you have a listen url in your moderation_backend.ini")
}
giteaToken := cfg.Section("gitea").Key("API_TOKEN").String()
giteaURL = cfg.Section("gitea").Key("URL").String()
if giteaToken == "" || giteaURL == "" {
fmt.Printf("Please make sure you have token and url in your moderation_backend.ini\n")
os.Exit(2)
log.Fatal("Please make sure you have token and url in your moderation_backend.ini")
}
jwtSecret := cfg.Section("security").Key("JWT_SECRET").String()
if jwtSecret == "" || jwtSecret == "default_jwt_secret" {
fmt.Printf("Please make sure you set a unique jwt secret in the moderation_backend.ini\n")
os.Exit(2)
log.Fatal("Please make sure you set a unique jwt secret in the moderation_backend.ini")
}
userMap := make(map[string]string)
for _, user := range cfg.Section("security.users").Keys() {
userMap[user.Name()] = user.String()
}
if len(userMap) == 0 {
fmt.Printf("Warning: You should add at least one user under the [user] section in moderation_backend.ini\n")
log.Warn("You should add at least one user under the [user] section in moderation_backend.ini")
}
mailer.MailConfig(cfg)
mailer.Config(cfg)
userConfig(cfg)
repoConfig(cfg)
log.Config(cfg)
// inits
giteaClient, err = gitea.NewClient(giteaURL, gitea.SetToken(giteaToken))
if err != nil {
fmt.Printf("could not connect to %s\n", giteaURL)
os.Exit(3)
log.Fatal("Could not connect[url=%q]: %v", giteaURL, err)
}
jwtAuth = jwtauth.New("HS256", []byte(jwtSecret), nil)
@ -134,10 +130,8 @@ func main() {
fs.ServeHTTP(w, r)
})
fmt.Printf("Starting app now on %s\n", appListenUrl)
if err := http.ListenAndServe(appListenUrl, r); err != nil {
fmt.Printf("Error: %v\n", err)
}
log.Info("Starting app now on %s", appListenUrl)
log.Fatal("http: %v", http.ListenAndServe(appListenUrl, r))
}
func login(w http.ResponseWriter, r *http.Request) {
@ -148,8 +142,9 @@ func login(w http.ResponseWriter, r *http.Request) {
func authRequire(ctx context.Context, requiredPermissionLevel PermissionLevel) error {
_, claims, err := jwtauth.FromContext(ctx)
if err != nil {
return err
return fmt.Errorf("jwtauth.FromContext: %v", err)
}
permissionLevel := PermissionLevel(claims["p"].(float64))
if permissionLevel < requiredPermissionLevel {
return &GenericError{

@ -5,6 +5,9 @@ LISTEN_URL = :3333
API_TOKEN = 44536456fefefeabc123547436587436fefefe4
URL = http://gitea:3000
[logging]
output = console
[security]
JWT_SECRET = default_jwt_secret

@ -2,8 +2,8 @@ package main
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"time"
"code.gitea.io/sdk/gitea"
@ -11,6 +11,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-ini/ini"
"codeberg.org/codeberg/moderation/internal/log"
"codeberg.org/codeberg/moderation/internal/mailer"
"codeberg.org/codeberg/moderation/internal/util"
)
@ -28,8 +29,8 @@ type RepoRequest struct {
}
func repoRoutes(r chi.Router) {
r.Post("/quarantine", repoQuarantine)
r.Post("/inform", repoInformOwner)
r.Post("/quarantine", util.ErrorMiddleware(repoQuarantine))
r.Post("/inform", util.ErrorMiddleware(repoInformOwner))
}
func repoConfig(cfg *ini.File) {
@ -42,97 +43,87 @@ func repoTransfer(currentOwner, currentName, newOwner, newName string) error {
return &GenericError{"repoTransfer", "some empty value passed"}
}
_, res, err := giteaClient.TransferRepo(currentOwner, currentName, gitea.TransferRepoOption{
_, _, err := giteaClient.TransferRepo(currentOwner, currentName, gitea.TransferRepoOption{
fnetX marked this conversation as resolved
Review

is the result status code included in the error?

is the result status code included in the error?
Review

No. But I personally don't see the benefit of the status code when you can instead print the error. Because go-sdk should convert the status code(best-effort) to a more generalized error.

No. But I _personally_ don't see the benefit of the status code when you can instead print the error. Because go-sdk should convert the status code(best-effort) to a more generalized error.
Review
Ref: https://gitea.com/gitea/go-sdk/src/commit/a56a62a4df29fde9a34ea2a09e9c351d3ea96699/gitea/client.go#L303
Review

I had an issue recently that I run code on already transferred repos, and the status code was 405 method not allowed. It didn't really help me with debugging, but the SDK message wasn't more helpful I think. I just hope that the improved error handling would have avoided this issue in the 1st place, so I don't object to leaving this as-is.

I had an issue recently that I run code on already transferred repos, and the status code was 405 method not allowed. It didn't really help me with debugging, but the SDK message wasn't more helpful I think. I just hope that the improved error handling would have avoided this issue in the 1st place, so I don't object to leaving this as-is.
NewOwner: newOwner,
})
if err != nil {
if res == nil {
return err
}
return &GenericError{"Transfer Repo: ", res.Status}
return err
}
_, res, err = giteaClient.EditRepo(newOwner, currentName, gitea.EditRepoOption{
_, _, err = giteaClient.EditRepo(newOwner, currentName, gitea.EditRepoOption{
Name: &newName,
})
if err != nil {
if res == nil {
return err
}
return &GenericError{"Change Repo Name: ", res.Status}
return err
}
return nil
}
func repoQuarantine(w http.ResponseWriter, r *http.Request) {
util.ErrorCatcher("repoQuarantine", w, r, func(w http.ResponseWriter, r *http.Request) (error, string) {
if err := authRequire(r.Context(), PERMISSION_MODERATOR); err != nil {
w.WriteHeader(403)
return err, "auth"
func repoQuarantine(w http.ResponseWriter, r *http.Request) error {
if err := authRequire(r.Context(), PERMISSION_MODERATOR); err != nil {
err = log.NewError("Not able to authenticate user: %v", err)
w.WriteHeader(http.StatusForbidden)
return err
}
request := RepoRequest{}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return log.NewError("Error decoding request's body as JSON: %v", err)
}
for _, repodata := range request.List {
giteaUser, _, err := giteaClient.GetUserInfo(repodata.Owner)
if err != nil {
return log.NewError("Not able to GetUserInfo[owner=%s]: %v", repodata.Owner, err)
}
request := RepoRequest{}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return err, "Error decoding JSON"
// Make the new name in the format of: unixTime_oldUserID_oldUserName_oldRepoName
newName := fmt.Sprintf("%d_%d_%s_%s", time.Now().Unix(), giteaUser.ID, repodata.Owner, repodata.Name)
if err = repoTransfer(repodata.Owner, repodata.Name, reposQuarantineNewOwner, newName); err != nil {
return log.NewError("repoTransfer[old_owner=%s old_repo_name=%s new_owner=%s new_repo_name=%s]: %v", repodata.Owner, repodata.Name, reposQuarantineNewOwner, newName, err)
}
for _, repodata := range request.List {
giteaUser, res, err := giteaClient.GetUserInfo(repodata.Owner)
if err != nil {
return err, "Get User Info: " + res.Status
}
newName := strconv.FormatInt(time.Now().Unix(), 10) + "_" + strconv.FormatInt(giteaUser.ID, 10) + "_" + repodata.Owner + "_" + repodata.Name
if err = repoTransfer(repodata.Owner, repodata.Name, reposQuarantineNewOwner, newName); err != nil {
return err, ""
}
mailTemplateData := make(map[string]interface{})
mailTemplateData["repoName"] = repodata.Name
mailTemplateData["repoOwner"] = repodata.Owner
if request.InformUser {
mailTemplateData["reason"] = request.InformUserAdditionalReason
}
if err = mailer.MailSend(giteaUser.Email, repodata.Owner, "quarantine", mailTemplateData); err != nil {
return err, "send email"
}
mailTemplateData := make(map[string]interface{})
mailTemplateData["repoName"] = repodata.Name
mailTemplateData["repoOwner"] = repodata.Owner
if request.InformUser {
mailTemplateData["reason"] = request.InformUserAdditionalReason
}
return nil, ""
})
if err = mailer.MailSend(giteaUser.Email, repodata.Owner, "quarantine", mailTemplateData); err != nil {
return log.NewError("MailSend[email=%s, owner=%s]: %v", giteaUser.Email, repodata.Owner, err)
}
}
return nil
}
func repoInformOwner(w http.ResponseWriter, r *http.Request) {
util.ErrorCatcher("repoInformOwner", w, r, func(w http.ResponseWriter, r *http.Request) (error, string) {
if err := authRequire(r.Context(), PERMISSION_MODERATOR); err != nil {
w.WriteHeader(403)
return err, "auth"
func repoInformOwner(w http.ResponseWriter, r *http.Request) error {
if err := authRequire(r.Context(), PERMISSION_MODERATOR); err != nil {
err = log.NewError("Not able to authenticate user: %v", err)
w.WriteHeader(http.StatusForbidden)
return err
}
request := RepoRequest{}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return log.NewError("Error decoding request's body as JSON: %v", err)
}
for _, repodata := range request.List {
giteaUser, _, err := giteaClient.GetUserInfo(repodata.Owner)
if err != nil {
return log.NewError("Not able to GetUserInfo[owner=%s]: %v", repodata.Owner, err)
}
request := RepoRequest{}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return err, "Error decoding JSON"
mailTemplateData := make(map[string]interface{})
mailTemplateData["repoName"] = repodata.Name
mailTemplateData["repoOwner"] = repodata.Owner
if request.InformUser {
mailTemplateData["reason"] = request.InformUserAdditionalReason
}
for _, repodata := range request.List {
giteaUser, res, err := giteaClient.GetUserInfo(repodata.Owner)
if err != nil {
if res == nil {
return err, "GetUserInfo"
} else {
return err, "Get User Info: " + res.Status
}
}
mailTemplateData := make(map[string]interface{})
mailTemplateData["repoName"] = repodata.Name
mailTemplateData["repoOwner"] = repodata.Owner
if request.InformUser {
mailTemplateData["reason"] = request.InformUserAdditionalReason
}
if err = mailer.MailSend(giteaUser.Email, repodata.Owner, "quarantine", mailTemplateData); err != nil {
return err, "send email"
}
if err := mailer.MailSend(giteaUser.Email, repodata.Owner, "quarantine", mailTemplateData); err != nil {
return log.NewError("MailSend[email=%s, owner=%s]: %v", giteaUser.Email, repodata.Owner, err)
}
return nil, ""
})
}
return nil
}

@ -10,6 +10,7 @@ import (
"github.com/go-chi/chi/v5"
"github.com/go-ini/ini"
"codeberg.org/codeberg/moderation/internal/log"
"codeberg.org/codeberg/moderation/internal/util"
)
@ -38,9 +39,9 @@ type UserActionRequest struct {
}
func userRoutes(r chi.Router) {
r.Get("/list", userList)
r.Get("/list/suspicious", userListSuspicious)
r.Post("/delete", userDelete)
r.Get("/list", util.ErrorMiddleware(userList))
r.Get("/list/suspicious", util.ErrorMiddleware(userListSuspicious))
r.Post("/delete", util.ErrorMiddleware(userDelete))
}
func userConfig(cfg *ini.File) {
@ -48,87 +49,92 @@ func userConfig(cfg *ini.File) {
suspiciousUserKeywords = userconfig.Key("SUSPICIOUS_USER_KEYWORDS").Strings(",")
}
func userList(w http.ResponseWriter, r *http.Request) {
util.ErrorCatcher("userList", w, r, func(w http.ResponseWriter, r *http.Request) (error, string) {
if err := authRequire(r.Context(), PERMISSION_MODERATOR); err != nil {
w.WriteHeader(403)
return err, "auth"
}
w.Header().Set("Content-Type", "text/json")
users, _, _ := giteaClient.AdminListUsers(gitea.AdminListUsersOptions{})
data, err := filterInto(&users, &UserSimple{})
if err != nil {
return err, "filterInto"
}
res, err := json.Marshal(data)
if err != nil {
return err, "json encoding"
}
_, _ = w.Write([]byte(string(res)))
return nil, ""
})
func userList(w http.ResponseWriter, r *http.Request) error {
if err := authRequire(r.Context(), PERMISSION_MODERATOR); err != nil {
err = log.NewError("Not able to authenticate user: %v", err)
w.WriteHeader(http.StatusForbidden)
return err
}
users, _, err := giteaClient.AdminListUsers(gitea.AdminListUsersOptions{})
if err != nil {
return log.NewError("Not able to list admin users: %v", err)
}
data, err := filterInto(&users, &UserSimple{})
if err != nil {
return log.NewError("Not able to filterInto user: %v", err)
}
res, err := json.Marshal(data)
if err != nil {
return log.NewError("json.Marshal: %v", err)
}
_, _ = w.Write(res)
return nil
}
func userListSuspicious(w http.ResponseWriter, r *http.Request) {
util.ErrorCatcher("userListSuspicious", w, r, func(w http.ResponseWriter, r *http.Request) (error, string) {
if err := authRequire(r.Context(), PERMISSION_MODERATOR); err != nil {
w.WriteHeader(403)
return err, "auth"
}
func userListSuspicious(w http.ResponseWriter, r *http.Request) error {
if err := authRequire(r.Context(), PERMISSION_MODERATOR); err != nil {
return log.NewError("Not able to authenticate user: %v", err)
}
users := []*gitea.User{}
// go through the keywords in random order to have alternating results
rand.Seed(time.Now().Unix())
for range suspiciousUserKeywords {
foundUsers, _, err := giteaClient.SearchUsers(gitea.SearchUsersOption{
KeyWord: suspiciousUserKeywords[rand.Intn(len(suspiciousUserKeywords))],
})
if err != nil {
return err, "SearchUsers"
}
users = append(users, foundUsers...)
if len(users) >= 100 {
break
}
}
data, err := filterInto(&users, &User{})
if err != nil {
return err, "filterInto"
}
users := []*gitea.User{}
res, err := json.Marshal(data)
if err != nil {
return err, "json.Marshal"
}
_, _ = w.Write([]byte(string(res)))
return nil, ""
suspiciousKeyword := make([]string, len(suspiciousUserKeywords))
copy(suspiciousKeyword, suspiciousUserKeywords)
// go through the keywords in random order to have alternating results
rand.Shuffle(len(suspiciousKeyword), func(i, j int) {
suspiciousKeyword[i], suspiciousKeyword[j] = suspiciousKeyword[j], suspiciousKeyword[i]
})
}
func userDelete(w http.ResponseWriter, r *http.Request) {
util.ErrorCatcher("userListSuspicious", w, r, func(w http.ResponseWriter, r *http.Request) (error, string) {
if err := authRequire(r.Context(), PERMISSION_ADMIN); err != nil {
w.WriteHeader(403)
return err, "auth"
for _, keyword := range suspiciousUserKeywords {
foundUsers, _, err := giteaClient.SearchUsers(gitea.SearchUsersOption{
KeyWord: keyword,
})
if err != nil {
return log.NewError("Not able to SearchUsers[keyword=%s]: %v", keyword, err)
}
request := UserActionRequest{}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return err, "Error decoding JSON"
users = append(users, foundUsers...)
if len(users) >= 100 {
break
}
}
data, err := filterInto(&users, &User{})
if err != nil {
return log.NewError("Not able to filterInto user: %v", err)
}
res, err := json.Marshal(data)
if err != nil {
return log.NewError("json.Marshal: %v", err)
}
_, _ = w.Write(res)
return nil
}
var errors []GenericError
for _, user := range request.List {
if _, err := giteaClient.AdminDeleteUser(user.UserName); err != nil {
errors = append(errors, GenericError{"Delete User", err.Error()})
}
}
func userDelete(w http.ResponseWriter, r *http.Request) error {
if err := authRequire(r.Context(), PERMISSION_ADMIN); err != nil {
return log.NewError("Not able to authenticate user: %v", err)
}
res, err := json.Marshal(errors)
if err != nil {
return err, "json.Marshal"
request := UserActionRequest{}
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
return log.NewError("Error decoding request's body as JSON: %v", err)
}
var errors []GenericError
for _, user := range request.List {
if _, err := giteaClient.AdminDeleteUser(user.UserName); err != nil {
errors = append(errors, GenericError{"Delete User", err.Error()})
}
_, _ = w.Write([]byte(string(res)))
return nil, ""
})
}
res, err := json.Marshal(errors)
if err != nil {
return log.NewError("json.Marshal: %v", err)
}
_, _ = w.Write(res)
return nil
}

Loading…
Cancel
Save