Feat: cmd user manager

pull/21/head
zijiren233 2 years ago
parent 0082218c90
commit d3abf4bc45

@ -0,0 +1,48 @@
package admin
import (
"errors"
"fmt"
"strconv"
"github.com/spf13/cobra"
"github.com/synctv-org/synctv/internal/bootstrap"
"github.com/synctv-org/synctv/internal/db"
)
var AddCmd = &cobra.Command{
Use: "add",
Short: "add admin by user id",
Long: `add admin by user id`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return bootstrap.New(bootstrap.WithContext(cmd.Context())).Add(
bootstrap.InitDiscardLog,
bootstrap.InitConfig,
bootstrap.InitDatabase,
).Run()
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("missing user id")
}
id, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid user id: %s", args[0])
}
u, err := db.GetUserByID(uint(id))
if err != nil {
fmt.Printf("get user failed: %s", err)
return nil
}
if err := db.AddAdmin(u); err != nil {
fmt.Printf("add admin failed: %s", err)
return nil
}
fmt.Printf("add admin success: %s\n", u.Username)
return nil
},
}
func init() {
AdminCmd.AddCommand(AddCmd)
}

@ -0,0 +1,11 @@
package admin
import (
"github.com/spf13/cobra"
)
var AdminCmd = &cobra.Command{
Use: "admin",
Short: "admin",
Long: `you must first shut down the server, otherwise the changes will not take effect.`,
}

@ -0,0 +1,48 @@
package admin
import (
"errors"
"fmt"
"strconv"
"github.com/spf13/cobra"
"github.com/synctv-org/synctv/internal/bootstrap"
"github.com/synctv-org/synctv/internal/db"
)
var RemoveCmd = &cobra.Command{
Use: "remove",
Short: "remove",
Long: `remove admin`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return bootstrap.New(bootstrap.WithContext(cmd.Context())).Add(
bootstrap.InitDiscardLog,
bootstrap.InitConfig,
bootstrap.InitDatabase,
).Run()
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("missing user id")
}
id, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid user id: %s", args[0])
}
u, err := db.GetUserByID(uint(id))
if err != nil {
fmt.Printf("get user failed: %s", err)
return nil
}
if err := db.RemoveAdmin(u); err != nil {
fmt.Printf("remove admin failed: %s", err)
return nil
}
fmt.Printf("remove admin success: %s\n", u.Username)
return nil
},
}
func init() {
AdminCmd.AddCommand(RemoveCmd)
}

@ -0,0 +1,33 @@
package admin
import (
"fmt"
"github.com/spf13/cobra"
"github.com/synctv-org/synctv/internal/bootstrap"
"github.com/synctv-org/synctv/internal/db"
)
var ShowCmd = &cobra.Command{
Use: "show",
Short: "show admin",
Long: `show admin`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return bootstrap.New(bootstrap.WithContext(cmd.Context())).Add(
bootstrap.InitDiscardLog,
bootstrap.InitConfig,
bootstrap.InitDatabase,
).Run()
},
RunE: func(cmd *cobra.Command, args []string) error {
admins := db.GetAdmins()
for _, admin := range admins {
fmt.Printf("id: %d\tusername: %s\n", admin.ID, admin.Username)
}
return nil
},
}
func init() {
AdminCmd.AddCommand(ShowCmd)
}

@ -7,14 +7,16 @@ import (
"github.com/mitchellh/go-homedir" "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/synctv-org/synctv/cmd/admin"
"github.com/synctv-org/synctv/cmd/flags" "github.com/synctv-org/synctv/cmd/flags"
"github.com/synctv-org/synctv/cmd/user"
"github.com/synctv-org/synctv/internal/version" "github.com/synctv-org/synctv/internal/version"
) )
var RootCmd = &cobra.Command{ var RootCmd = &cobra.Command{
Use: "synctv-server", Use: "synctv",
Short: "synctv-server", Short: "synctv",
Long: `synctv-server https://github.com/synctv-org/synctv`, Long: `synctv https://github.com/synctv-org/synctv`,
} }
func Execute() { func Execute() {
@ -38,3 +40,8 @@ func init() {
RootCmd.PersistentFlags().StringVar(&flags.DataDir, "data-dir", filepath.Join(home, ".synctv"), "data dir") RootCmd.PersistentFlags().StringVar(&flags.DataDir, "data-dir", filepath.Join(home, ".synctv"), "data dir")
RootCmd.PersistentFlags().StringVarP(&flags.ConfigFile, "config", "f", "", "config file path") RootCmd.PersistentFlags().StringVarP(&flags.ConfigFile, "config", "f", "", "config file path")
} }
func init() {
RootCmd.AddCommand(admin.AdminCmd)
RootCmd.AddCommand(user.UserCmd)
}

@ -0,0 +1,49 @@
package user
import (
"errors"
"fmt"
"strconv"
"github.com/spf13/cobra"
"github.com/synctv-org/synctv/internal/bootstrap"
"github.com/synctv-org/synctv/internal/db"
)
var BanCmd = &cobra.Command{
Use: "ban",
Short: "ban user with user id",
Long: "ban user with user id",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return bootstrap.New(bootstrap.WithContext(cmd.Context())).Add(
bootstrap.InitDiscardLog,
bootstrap.InitConfig,
bootstrap.InitDatabase,
).Run()
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("missing user id")
}
id, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid user id: %s", args[0])
}
u, err := db.GetUserByID(uint(id))
if err != nil {
fmt.Printf("get user failed: %s\n", err)
return nil
}
err = db.BanUser(u)
if err != nil {
fmt.Printf("ban user failed: %s\n", err)
return nil
}
fmt.Printf("ban user success: %s\n", u.Username)
return nil
},
}
func init() {
UserCmd.AddCommand(BanCmd)
}

@ -0,0 +1,44 @@
package user
import (
"errors"
"fmt"
"strconv"
"github.com/spf13/cobra"
"github.com/synctv-org/synctv/internal/bootstrap"
"github.com/synctv-org/synctv/internal/db"
)
var DeleteCmd = &cobra.Command{
Use: "delete",
Short: "delete",
Long: `delete user`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return bootstrap.New(bootstrap.WithContext(cmd.Context())).Add(
bootstrap.InitDiscardLog,
bootstrap.InitConfig,
bootstrap.InitDatabase,
).Run()
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("missing user id")
}
id, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid user id: %s", args[0])
}
u, err := db.LoadAndDeleteUserByID(uint(id))
if err != nil {
fmt.Printf("delete user failed: %s\n", err)
return nil
}
fmt.Printf("delete user success: %s\n", u.Username)
return nil
},
}
func init() {
UserCmd.AddCommand(DeleteCmd)
}

@ -0,0 +1,44 @@
package user
import (
"errors"
"fmt"
"github.com/spf13/cobra"
"github.com/synctv-org/synctv/internal/bootstrap"
"github.com/synctv-org/synctv/internal/db"
)
var SearchCmd = &cobra.Command{
Use: "search",
Short: "search user by id or username",
Long: `search user by id or username`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return bootstrap.New(bootstrap.WithContext(cmd.Context())).Add(
bootstrap.InitDiscardLog,
bootstrap.InitConfig,
bootstrap.InitDatabase,
).Run()
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("missing user id or username")
}
us, err := db.GetUserByIDOrUsernameLike(args[0])
if err != nil {
return err
}
if len(us) == 0 {
fmt.Println("user not found")
return nil
}
for _, u := range us {
fmt.Printf("id: %d\tusername: %s\tcreated_at: %s\trole: %s\n", u.ID, u.Username, u.CreatedAt, u.Role)
}
return nil
},
}
func init() {
UserCmd.AddCommand(SearchCmd)
}

@ -0,0 +1,49 @@
package user
import (
"errors"
"fmt"
"strconv"
"github.com/spf13/cobra"
"github.com/synctv-org/synctv/internal/bootstrap"
"github.com/synctv-org/synctv/internal/db"
)
var UnbanCmd = &cobra.Command{
Use: "unban",
Short: "unban user with user id",
Long: "unban user with user id",
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return bootstrap.New(bootstrap.WithContext(cmd.Context())).Add(
bootstrap.InitDiscardLog,
bootstrap.InitConfig,
bootstrap.InitDatabase,
).Run()
},
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
return errors.New("missing user id")
}
id, err := strconv.Atoi(args[0])
if err != nil {
return fmt.Errorf("invalid user id: %s", args[0])
}
u, err := db.GetUserByID(uint(id))
if err != nil {
fmt.Printf("get user failed: %s\n", err)
return nil
}
err = db.UnbanUser(u)
if err != nil {
fmt.Printf("unban user failed: %s", err)
return nil
}
fmt.Printf("unban user success: %s\n", u.Username)
return nil
},
}
func init() {
UserCmd.AddCommand(UnbanCmd)
}

@ -0,0 +1,9 @@
package user
import "github.com/spf13/cobra"
var UserCmd = &cobra.Command{
Use: "user",
Short: "user",
Long: `you must first shut down the server, otherwise the changes will not take effect.`,
}

@ -67,6 +67,14 @@ func InitLog(ctx context.Context) error {
} }
func InitStdLog(ctx context.Context) error { func InitStdLog(ctx context.Context) error {
logrus.StandardLogger().SetOutput(os.Stdout)
log.SetOutput(os.Stdout)
setLog(logrus.StandardLogger()) setLog(logrus.StandardLogger())
return nil return nil
} }
func InitDiscardLog(ctx context.Context) error {
logrus.StandardLogger().SetOutput(io.Discard)
log.SetOutput(io.Discard)
return nil
}

@ -19,7 +19,7 @@ var (
func Init(d *gorm.DB, t conf.DatabaseType) error { func Init(d *gorm.DB, t conf.DatabaseType) error {
db = d db = d
dbType = t dbType = t
return AutoMigrate(new(model.Movie), new(model.Room), new(model.User), new(model.RoomUserRelation), new(model.UserProvider)) return AutoMigrate(new(model.Movie), new(model.Room), new(model.User), new(model.RoomUserRelation), new(model.UserProvider), new(model.SettingItem))
} }
func AutoMigrate(dst ...any) error { func AutoMigrate(dst ...any) error {

@ -0,0 +1,42 @@
package db
import (
"github.com/synctv-org/synctv/internal/model"
"gorm.io/gorm/clause"
)
func GetSettingItems() ([]*model.SettingItem, error) {
var items []*model.SettingItem
err := db.Find(&items).Error
return items, err
}
func GetSettingItemByName(name string) (*model.SettingItem, error) {
var item model.SettingItem
err := db.Where("name = ?", name).First(&item).Error
return &item, err
}
func SaveSettingItem(item *model.SettingItem) error {
return db.Clauses(clause.OnConflict{
UpdateAll: true,
}).Save(item).Error
}
func DeleteSettingItem(item *model.SettingItem) error {
return db.Delete(item).Error
}
func DeleteSettingItemByName(name string) error {
return db.Where("name = ?", name).Delete(&model.SettingItem{}).Error
}
func GetSettingItemValue(name string) (string, error) {
var value string
err := db.Model(&model.SettingItem{}).Where("name = ?", name).Select("value").First(&value).Error
return value, err
}
func SetSettingItemValue(name, value string) error {
return db.Model(&model.SettingItem{}).Where("name = ?", name).Update("value", value).Error
}

@ -105,6 +105,15 @@ func GerUsersIDByUsernameLike(username string, scopes ...func(*gorm.DB) *gorm.DB
return ids return ids
} }
func GetUserByIDOrUsernameLike(idOrUsername string, scopes ...func(*gorm.DB) *gorm.DB) ([]*model.User, error) {
var users []*model.User
err := db.Where("id = ? OR username LIKE ?", idOrUsername, fmt.Sprintf("%%%s%%", idOrUsername)).Scopes(scopes...).Find(&users).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return users, errors.New("user not found")
}
return users, err
}
func GetUserByID(id uint) (*model.User, error) { func GetUserByID(id uint) (*model.User, error) {
u := &model.User{} u := &model.User{}
err := db.Where("id = ?", id).First(u).Error err := db.Where("id = ?", id).First(u).Error
@ -123,6 +132,38 @@ func GetUsersByRoomID(roomID uint, scopes ...func(*gorm.DB) *gorm.DB) ([]model.U
return users, err return users, err
} }
func BanUser(u *model.User) error {
if u.Role == model.RoleBanned {
return nil
}
u.Role = model.RoleBanned
return SaveUser(u)
}
func BanUserByID(userID uint) error {
err := db.Model(&model.User{}).Where("id = ?", userID).Update("role", model.RoleBanned).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("user not found")
}
return err
}
func UnbanUser(u *model.User) error {
if u.Role != model.RoleBanned {
return errors.New("user is not banned")
}
u.Role = model.RoleUser
return SaveUser(u)
}
func UnbanUserByID(userID uint) error {
err := db.Model(&model.User{}).Where("id = ?", userID).Update("role", model.RoleUser).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("user not found")
}
return err
}
func DeleteUserByID(userID uint) error { func DeleteUserByID(userID uint) error {
err := db.Unscoped().Delete(&model.User{}, userID).Error err := db.Unscoped().Delete(&model.User{}, userID).Error
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
@ -133,14 +174,13 @@ func DeleteUserByID(userID uint) error {
func LoadAndDeleteUserByID(userID uint, columns ...clause.Column) (*model.User, error) { func LoadAndDeleteUserByID(userID uint, columns ...clause.Column) (*model.User, error) {
u := &model.User{} u := &model.User{}
err := db.Unscoped(). if db.Unscoped().
Clauses(clause.Returning{Columns: columns}). Clauses(clause.Returning{Columns: columns}).
Delete(u, userID). Delete(u, userID).
Error RowsAffected == 0 {
if errors.Is(err, gorm.ErrRecordNotFound) {
return u, errors.New("user not found") return u, errors.New("user not found")
} }
return u, err return u, nil
} }
func DeleteUserByUsername(username string) error { func DeleteUserByUsername(username string) error {
@ -183,3 +223,25 @@ func SetUserHashedPassword(userID uint, hashedPassword []byte) error {
func SaveUser(u *model.User) error { func SaveUser(u *model.User) error {
return db.Save(u).Error return db.Save(u).Error
} }
func AddAdmin(u *model.User) error {
if u.Role >= model.RoleAdmin {
return nil
}
u.Role = model.RoleAdmin
return SaveUser(u)
}
func RemoveAdmin(u *model.User) error {
if u.Role < model.RoleAdmin {
return nil
}
u.Role = model.RoleUser
return SaveUser(u)
}
func GetAdmins() []*model.User {
var users []*model.User
db.Where("role >= ?", model.RoleAdmin).Find(&users)
return users
}

@ -0,0 +1,6 @@
package model
type SettingItem struct {
Name string `gorm:"primaryKey"`
Value string
}

@ -14,9 +14,21 @@ const (
RoleBanned Role = iota RoleBanned Role = iota
RoleUser RoleUser
RoleAdmin RoleAdmin
RoleRoot
) )
func (r Role) String() string {
switch r {
case RoleBanned:
return "banned"
case RoleUser:
return "user"
case RoleAdmin:
return "admin"
default:
return "unknown"
}
}
type User struct { type User struct {
ID uint `gorm:"primarykey"` ID uint `gorm:"primarykey"`
CreatedAt time.Time CreatedAt time.Time

@ -23,6 +23,9 @@ func (u *User) NewMovie(movie model.MovieInfo) model.Movie {
} }
func (u *User) HasPermission(roomID uint, permission model.Permission) bool { func (u *User) HasPermission(roomID uint, permission model.Permission) bool {
if u.Role == model.RoleAdmin {
return true
}
ur, err := db.GetRoomUserRelation(roomID, u.ID) ur, err := db.GetRoomUserRelation(roomID, u.ID)
if err != nil { if err != nil {
return false return false

@ -8,6 +8,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
"github.com/synctv-org/synctv/internal/conf" "github.com/synctv-org/synctv/internal/conf"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op" "github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/server/model" "github.com/synctv-org/synctv/server/model"
"github.com/zijiren233/stream" "github.com/zijiren233/stream"
@ -75,6 +76,9 @@ func AuthRoom(Authorization string) (*op.User, *op.Room, error) {
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
if u.Role == dbModel.RoleBanned {
return nil, nil, errors.New("user banned")
}
r, err := op.LoadOrInitRoomByID(claims.RoomId) r, err := op.LoadOrInitRoomByID(claims.RoomId)
if err != nil { if err != nil {
@ -101,11 +105,17 @@ func AuthUser(Authorization string) (*op.User, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if u.Role == dbModel.RoleBanned {
return nil, errors.New("user banned")
}
return u, nil return u, nil
} }
func NewAuthUserToken(user *op.User) (string, error) { func NewAuthUserToken(user *op.User) (string, error) {
if user.Role == dbModel.RoleBanned {
return "", errors.New("user banned")
}
t, err := time.ParseDuration(conf.Conf.Jwt.Expire) t, err := time.ParseDuration(conf.Conf.Jwt.Expire)
if err != nil { if err != nil {
return "", err return "", err
@ -121,6 +131,9 @@ func NewAuthUserToken(user *op.User) (string, error) {
} }
func NewAuthRoomToken(user *op.User, room *op.Room) (string, error) { func NewAuthRoomToken(user *op.User, room *op.Room) (string, error) {
if user.Role == dbModel.RoleBanned {
return "", errors.New("user banned")
}
t, err := time.ParseDuration(conf.Conf.Jwt.Expire) t, err := time.ParseDuration(conf.Conf.Jwt.Expire)
if err != nil { if err != nil {
return "", err return "", err

Loading…
Cancel
Save