108 lines
2.8 KiB
Go
108 lines
2.8 KiB
Go
// 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 cmd
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/urfave/cli/v2"
|
|
|
|
"github.com/tamakyi/TamaBox/internal/conf"
|
|
"github.com/tamakyi/TamaBox/internal/db"
|
|
)
|
|
|
|
// Ban 封禁用户并连带清理其全部数据。
|
|
// 用法:TamaBox ban --email [email protected] 或 TamaBox ban --domain someone
|
|
var Ban = &cli.Command{
|
|
Name: "ban",
|
|
Usage: "Ban a user and clean up all their questions, follow-ups and chats",
|
|
Action: runBan,
|
|
Flags: []cli.Flag{
|
|
&cli.StringFlag{
|
|
Name: "email",
|
|
Usage: "Email of the user to ban",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "domain",
|
|
Usage: "Custom domain of the user to ban",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "yes",
|
|
Usage: "Skip confirmation",
|
|
},
|
|
},
|
|
}
|
|
|
|
func runBan(ctx *cli.Context) error {
|
|
if err := conf.Init(); err != nil {
|
|
return errors.Wrap(err, "load configuration")
|
|
}
|
|
|
|
dbType := conf.Database.Type
|
|
if dbType == "postgres" {
|
|
conf.Database.DSN = conf.PostgresDsn()
|
|
} else {
|
|
conf.Database.DSN = conf.MySQLDsn()
|
|
}
|
|
|
|
database, err := db.Init(dbType, conf.Database.DSN)
|
|
if err != nil {
|
|
return errors.Wrap(err, "connect to database")
|
|
}
|
|
|
|
email := ctx.String("email")
|
|
domain := ctx.String("domain")
|
|
if email == "" && domain == "" {
|
|
return errors.New("must provide --email or --domain")
|
|
}
|
|
|
|
// 用 Unscoped 查找,支持封禁已软删除(自助停用)的账号
|
|
var user db.User
|
|
query := database.WithContext(ctx.Context).Unscoped()
|
|
if email != "" {
|
|
query = query.Where("email = ?", email)
|
|
} else {
|
|
query = query.Where("domain = ?", domain)
|
|
}
|
|
if err := query.First(&user).Error; err != nil {
|
|
return errors.Wrap(err, "find user to ban")
|
|
}
|
|
|
|
logrus.WithFields(logrus.Fields{
|
|
"id": user.ID,
|
|
"name": user.Name,
|
|
"email": user.Email,
|
|
"domain": user.Domain,
|
|
}).Warn("准备封禁以下账号,其提问/追问/私聊将一并删除,此操作不可恢复!")
|
|
|
|
if !ctx.Bool("yes") {
|
|
logrus.Warn("输入大写 YES 确认执行封禁:")
|
|
var answer string
|
|
if _, err := fmtScan(&answer); err != nil {
|
|
return errors.Wrap(err, "read confirmation")
|
|
}
|
|
if answer != "YES" {
|
|
return errors.New("已取消")
|
|
}
|
|
}
|
|
|
|
if err := db.Users.BanUserAndCleanup(ctx.Context, user.ID); err != nil {
|
|
return errors.Wrap(err, "ban user and cleanup")
|
|
}
|
|
|
|
// 数据库事务已经提交,先明确输出这一事实,再进行非事务性的 R2 清理。
|
|
logrus.WithField("user_id", user.ID).Info("数据库封禁与关联数据清理已提交")
|
|
drainR2OutboxAfterBan(ctx.Context)
|
|
return nil
|
|
}
|
|
|
|
// fmtScan 从标准输入读取一行,便于交互确认(隔离以便测试)。
|
|
var fmtScan = func(dst *string) (int, error) {
|
|
return fmt.Fscan(os.Stdin, dst)
|
|
}
|