Feat: root and admin role api

pull/21/head
zijiren233 2 years ago
parent 66ba37ac59
commit eb66cbe655

@ -9,6 +9,7 @@ import (
"github.com/spf13/cobra"
"github.com/synctv-org/synctv/cmd/admin"
"github.com/synctv-org/synctv/cmd/flags"
"github.com/synctv-org/synctv/cmd/root"
"github.com/synctv-org/synctv/cmd/setting"
"github.com/synctv-org/synctv/cmd/user"
"github.com/synctv-org/synctv/internal/version"
@ -46,4 +47,5 @@ func init() {
RootCmd.AddCommand(admin.AdminCmd)
RootCmd.AddCommand(user.UserCmd)
RootCmd.AddCommand(setting.SettingCmd)
RootCmd.AddCommand(root.RootCmd)
}

@ -0,0 +1,48 @@
package root
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 root by user id",
Long: `add root 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.AddRoot(u); err != nil {
fmt.Printf("add root failed: %s", err)
return nil
}
fmt.Printf("add root success: %s\n", u.Username)
return nil
},
}
func init() {
RootCmd.AddCommand(AddCmd)
}

@ -0,0 +1,48 @@
package root
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 root`,
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.RemoveRoot(u); err != nil {
fmt.Printf("remove root failed: %s", err)
return nil
}
fmt.Printf("remove root success: %s\n", u.Username)
return nil
},
}
func init() {
RootCmd.AddCommand(RemoveCmd)
}

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

@ -0,0 +1,33 @@
package root
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 root",
Long: `show root`,
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 {
roots := db.GetRoots()
for _, root := range roots {
fmt.Printf("id: %d\tusername: %s\n", root.ID, root.Username)
}
return nil
},
}
func init() {
RootCmd.AddCommand(ShowCmd)
}

@ -162,6 +162,17 @@ func WhereRoomNameLike(name string) func(db *gorm.DB) *gorm.DB {
}
}
func WhereUserNameLike(name string) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
switch dbType {
case conf.DatabaseTypePostgres:
return db.Where("username ILIKE ?", utils.LIKE(name))
default:
return db.Where("username LIKE ?", utils.LIKE(name))
}
}
}
func WhereCreatorIDIn(ids []uint) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("creator_id IN ?", ids)

@ -245,7 +245,7 @@ func RemoveAdmin(u *model.User) error {
func GetAdmins() []*model.User {
var users []*model.User
db.Where("role >= ?", model.RoleAdmin).Find(&users)
db.Where("role == ?", model.RoleAdmin).Find(&users)
return users
}
@ -315,3 +315,15 @@ func SetRoleByID(userID uint, role model.Role) error {
}
return err
}
func GetAllUserWithRoleUser(role model.Role, scopes ...func(*gorm.DB) *gorm.DB) []*model.User {
users := []*model.User{}
db.Where("role = ?", role).Scopes(scopes...).Find(&users)
return users
}
func GetAllUserWithRoleUserCount(scopes ...func(*gorm.DB) *gorm.DB) int64 {
var count int64
db.Model(&model.User{}).Where("role = ?", model.RoleUser).Scopes(scopes...).Count(&count)
return count
}

@ -115,3 +115,12 @@ func GetUserName(userID uint) string {
}
return u.Username
}
func SetRoleByID(userID uint, role model.Role) error {
err := db.SetRoleByID(userID, role)
if err != nil {
return err
}
userCache.Remove(userID)
return nil
}

@ -4,10 +4,11 @@ import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/synctv-org/synctv/internal/db"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/internal/settings"
"github.com/synctv-org/synctv/server/model"
"gorm.io/gorm"
)
func EditAdminSettings(ctx *gin.Context) {
@ -56,46 +57,68 @@ func AdminSettings(ctx *gin.Context) {
ctx.JSON(http.StatusOK, model.NewApiDataResp(resp))
}
func AddAdmin(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.User)
if !user.IsRoot() {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("permission denied"))
func Users(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.User)
order := ctx.Query("order")
if order == "" {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("order is required"))
return
}
req := model.IdReq{}
if err := model.Decode(ctx, &req); err != nil {
page, pageSize, err := GetPageAndPageSize(ctx)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if err := user.SetRole(dbModel.RoleAdmin); err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
ctx.Status(http.StatusNoContent)
}
var desc = ctx.DefaultQuery("sort", "desc") == "desc"
func DeleteAdmin(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.User)
scopes := []func(db *gorm.DB) *gorm.DB{}
if !user.IsRoot() {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("permission denied"))
return
if keyword := ctx.Query("keyword"); keyword != "" {
scopes = append(scopes, db.WhereUserNameLike(keyword))
}
req := model.IdReq{}
if err := model.Decode(ctx, &req); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
switch order {
case "createdAt":
if desc {
scopes = append(scopes, db.OrderByCreatedAtDesc)
} else {
scopes = append(scopes, db.OrderByCreatedAtAsc)
}
case "name":
if desc {
scopes = append(scopes, db.OrderByDesc("username"))
} else {
scopes = append(scopes, db.OrderByAsc("username"))
}
case "id":
if desc {
scopes = append(scopes, db.OrderByIDDesc)
} else {
scopes = append(scopes, db.OrderByIDAsc)
}
default:
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support order"))
return
}
if err := user.SetRole(dbModel.RoleUser); err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": db.GetAllUserWithRoleUserCount(scopes...),
"list": genUserListResp(dbModel.RoleUser, append(scopes, db.Paginate(page, pageSize))...),
}))
}
ctx.Status(http.StatusNoContent)
func genUserListResp(role dbModel.Role, scopes ...func(db *gorm.DB) *gorm.DB) []*model.UserInfoResp {
us := db.GetAllUserWithRoleUser(role, scopes...)
resp := make([]*model.UserInfoResp, len(us))
for i, v := range us {
resp[i] = &model.UserInfoResp{
ID: v.ID,
Username: v.Username,
Role: v.Role,
CreatedAt: v.CreatedAt.UnixMilli(),
}
}
return resp
}

@ -39,11 +39,25 @@ func Init(e *gin.Engine) {
{
admin := api.Group("/admin")
root := api.Group("/admin")
admin.Use(middlewares.AuthAdminMiddleware)
root.Use(middlewares.AuthRootMiddleware)
admin.GET("/settings/:group", AdminSettings)
{
admin.GET("/settings/:group", AdminSettings)
admin.POST("/settings", EditAdminSettings)
admin.GET("/users", Users)
}
admin.POST("/settings", EditAdminSettings)
{
root.GET("/admins", Admins)
root.POST("/addAdmin", AddAdmin)
root.POST("deleteAdmin", DeleteAdmin)
}
}
{

@ -114,7 +114,6 @@ func RoomList(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
resp := make([]*model.RoomListResp, 0, pageSize)
var desc = ctx.DefaultQuery("sort", "desc") == "desc"
@ -177,24 +176,24 @@ func RoomList(ctx *gin.Context) {
return
}
resp = genRoomListResp(resp, append(scopes, db.Paginate(page, pageSize))...)
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": db.GetAllRoomsWithoutHiddenCount(scopes...),
"list": resp,
"list": genRoomListResp(append(scopes, db.Paginate(page, pageSize))...),
}))
}
func genRoomListResp(resp []*model.RoomListResp, scopes ...func(db *gorm.DB) *gorm.DB) []*model.RoomListResp {
for _, r := range db.GetAllRoomsWithoutHidden(scopes...) {
resp = append(resp, &model.RoomListResp{
func genRoomListResp(scopes ...func(db *gorm.DB) *gorm.DB) []*model.RoomListResp {
rs := db.GetAllRoomsWithoutHidden(scopes...)
resp := make([]*model.RoomListResp, len(rs))
for i, r := range rs {
resp[i] = &model.RoomListResp{
RoomId: r.ID,
RoomName: r.Name,
PeopleNum: op.ClientNum(r.ID),
NeedPassword: len(r.HashedPassword) != 0,
Creator: op.GetUserName(r.CreatorID),
CreatedAt: r.CreatedAt.UnixMilli(),
})
}
}
return resp
}

@ -0,0 +1,89 @@
package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/synctv-org/synctv/internal/db"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op"
"github.com/synctv-org/synctv/server/model"
)
func Admins(ctx *gin.Context) {
// user := ctx.MustGet("user").(*op.User)
u := db.GetAdmins()
us := make([]model.UserInfoResp, len(u))
for i, v := range u {
us[i] = model.UserInfoResp{
ID: v.ID,
Username: v.Username,
Role: v.Role,
CreatedAt: v.CreatedAt.UnixMilli(),
}
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(us))
}
func AddAdmin(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.User)
req := model.IdReq{}
if err := model.Decode(ctx, &req); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if req.Id == user.ID {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot add yourself"))
return
}
u, err := op.GetUserById(req.Id)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("user not found"))
return
}
if u.Role >= dbModel.RoleAdmin {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("user is already admin"))
return
}
if err := u.SetRole(dbModel.RoleAdmin); err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
ctx.Status(http.StatusNoContent)
}
func DeleteAdmin(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.User)
req := model.IdReq{}
if err := model.Decode(ctx, &req); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if req.Id == user.ID {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot remove yourself"))
return
}
u, err := op.GetUserById(req.Id)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("user not found"))
return
}
if u.Role == dbModel.RoleRoot {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot remove root"))
return
}
if err := u.SetRole(dbModel.RoleUser); err != nil {
ctx.AbortWithError(http.StatusInternalServerError, err)
return
}
ctx.Status(http.StatusNoContent)
}

@ -42,7 +42,6 @@ func UserRooms(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
resp := make([]*model.RoomListResp, 0, pageSize)
var desc = ctx.DefaultQuery("sort", "desc") == "desc"
@ -101,10 +100,8 @@ func UserRooms(ctx *gin.Context) {
return
}
resp = genRoomListResp(resp, append(scopes, db.Paginate(page, pageSize))...)
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": db.GetAllRoomsWithoutHiddenCount(scopes...),
"list": resp,
"list": genRoomListResp(append(scopes, db.Paginate(page, pageSize))...),
}))
}

@ -123,6 +123,17 @@ func AuthAdmin(Authorization string) (*op.User, error) {
return u, nil
}
func AuthRoot(Authorization string) (*op.User, error) {
u, err := AuthUser(Authorization)
if err != nil {
return nil, err
}
if !u.IsRoot() {
return nil, errors.New("user is not admin")
}
return u, nil
}
func NewAuthUserToken(user *op.User) (string, error) {
if user.IsBanned() {
return "", errors.New("user banned")
@ -196,3 +207,14 @@ func AuthAdminMiddleware(ctx *gin.Context) {
ctx.Set("user", user)
ctx.Next()
}
func AuthRootMiddleware(ctx *gin.Context) {
user, err := AuthRoot(ctx.GetHeader("Authorization"))
if err != nil {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err))
return
}
ctx.Set("user", user)
ctx.Next()
}

@ -5,6 +5,7 @@ import (
"github.com/gin-gonic/gin"
json "github.com/json-iterator/go"
dbModel "github.com/synctv-org/synctv/internal/model"
)
type SetUserPasswordReq struct {
@ -77,3 +78,10 @@ func (s *SignupUserReq) Validate() error {
}
return nil
}
type UserInfoResp struct {
ID uint `json:"id"`
Username string `json:"username"`
Role dbModel.Role `json:"role"`
CreatedAt int64 `json:"createdAt"`
}

Loading…
Cancel
Save