Feat: ban user and room

pull/24/head
zijiren233 2 years ago
parent 8a52618fed
commit 1d30a70539

@ -184,3 +184,9 @@ func Select(columns ...string) func(db *gorm.DB) *gorm.DB {
return db.Select(columns) return db.Select(columns)
} }
} }
func WhereStatus(status model.RoomStatus) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("status = ?", status)
}
}

@ -195,3 +195,11 @@ func GetAllRoomsByUserID(userID string) []*model.Room {
db.Where("creator_id = ?", userID).Find(&rooms) db.Where("creator_id = ?", userID).Find(&rooms)
return rooms return rooms
} }
func SetRoomStatus(roomID string, status model.RoomStatus) error {
err := db.Model(&model.Room{}).Where("id = ?", roomID).Update("status", status).Error
if err != nil && errors.Is(err, gorm.ErrRecordNotFound) {
return errors.New("room not found")
}
return err
}

@ -49,3 +49,19 @@ func (r *Room) NeedPassword() bool {
func (r *Room) CheckPassword(password string) bool { func (r *Room) CheckPassword(password string) bool {
return !r.NeedPassword() || bcrypt.CompareHashAndPassword(r.HashedPassword, stream.StringToBytes(password)) == nil return !r.NeedPassword() || bcrypt.CompareHashAndPassword(r.HashedPassword, stream.StringToBytes(password)) == nil
} }
func (r *Room) IsBanned() bool {
return r.Status == RoomStatusBanned
}
func (r *Room) IsPending() bool {
return r.Status == RoomStatusPending
}
func (r *Room) IsStopped() bool {
return r.Status == RoomStatusStopped
}
func (r *Room) IsActive() bool {
return r.Status == RoomStatusActive
}

@ -49,7 +49,11 @@ func (u *User) IsRoot() bool {
} }
func (u *User) IsAdmin() bool { func (u *User) IsAdmin() bool {
return u.Role == RoleAdmin return u.Role == RoleAdmin || u.IsRoot()
}
func (u *User) IsPending() bool {
return u.Role == RolePending
} }
func (u *User) IsBanned() bool { func (u *User) IsBanned() bool {

@ -182,3 +182,16 @@ func (r *Room) SetStatus(playing bool, seek float64, rate float64, timeDiff floa
func (r *Room) SetSeekRate(seek float64, rate float64, timeDiff float64) Status { func (r *Room) SetSeekRate(seek float64, rate float64, timeDiff float64) Status {
return r.current.SetSeekRate(seek, rate, timeDiff) return r.current.SetSeekRate(seek, rate, timeDiff)
} }
func (r *Room) SetRoomStatus(status model.RoomStatus) error {
err := db.SetRoomStatus(r.ID, status)
if err != nil {
return err
}
r.Status = status
switch status {
case model.RoomStatusBanned, model.RoomStatusStopped, model.RoomStatusPending:
return CompareAndCloseRoom(r)
}
return nil
}

@ -39,7 +39,21 @@ func InitRoom(room *model.Room) (*Room, error) {
return i.Value(), nil return i.Value(), nil
} }
var (
ErrRoomPending = errors.New("room pending, please wait for admin to approve")
ErrRoomStopped = errors.New("room stopped")
ErrRoomBanned = errors.New("room banned")
)
func LoadOrInitRoom(room *model.Room) (*Room, error) { func LoadOrInitRoom(room *model.Room) (*Room, error) {
switch room.Status {
case model.RoomStatusBanned:
return nil, ErrRoomBanned
case model.RoomStatusPending:
return nil, ErrRoomPending
case model.RoomStatusStopped:
return nil, ErrRoomStopped
}
t := time.Duration(settings.RoomTTL.Get()) t := time.Duration(settings.RoomTTL.Get())
i, loaded := roomCache.LoadOrStore(room.ID, &Room{ i, loaded := roomCache.LoadOrStore(room.ID, &Room{
Room: *room, Room: *room,
@ -52,14 +66,6 @@ func LoadOrInitRoom(room *model.Room) (*Room, error) {
if loaded { if loaded {
i.SetExpiration(time.Now().Add(t)) i.SetExpiration(time.Now().Add(t))
} }
switch room.Status {
case model.RoomStatusBanned:
return nil, errors.New("room banned")
case model.RoomStatusPending:
return nil, errors.New("room pending, please wait for admin to approve")
case model.RoomStatusStopped:
return nil, errors.New("room stopped")
}
return i.Value(), nil return i.Value(), nil
} }
@ -79,6 +85,19 @@ func CloseRoom(roomID string) error {
return nil return nil
} }
func CompareAndCloseRoom(room *Room) error {
r, loaded := roomCache.Load(room.ID)
if loaded {
if r.Value() != room {
return nil
}
if roomCache.CompareAndDelete(room.ID, r) {
r.Value().close()
}
}
return nil
}
func LoadRoomByID(id string) (*Room, error) { func LoadRoomByID(id string) (*Room, error) {
r2, loaded := roomCache.Load(id) r2, loaded := roomCache.Load(id)
if loaded { if loaded {

@ -13,6 +13,11 @@ import (
var userCache gcache.Cache var userCache gcache.Cache
var (
ErrUserBanned = errors.New("user banned")
ErrUserPending = errors.New("user pending, please wait for admin to approve")
)
func GetUserById(id string) (*User, error) { func GetUserById(id string) (*User, error) {
i, err := userCache.Get(id) i, err := userCache.Get(id)
if err == nil { if err == nil {
@ -24,6 +29,13 @@ func GetUserById(id string) (*User, error) {
return nil, err return nil, err
} }
switch u.Role {
case model.RoleBanned:
return nil, ErrUserBanned
case model.RolePending:
return nil, ErrUserPending
}
u2 := &User{ u2 := &User{
User: *u, User: *u,
} }

@ -172,18 +172,31 @@ func PendingUsers(ctx *gin.Context) {
})) }))
} }
func ApprovePendingUser(Authorization, userID string) error { func ApprovePendingUser(ctx *gin.Context) {
user, err := op.GetUserById(userID) req := model.UserIDReq{}
if err := model.Decode(ctx, &req); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
user, err := db.GetUserByID(req.ID)
if err != nil { if err != nil {
return err ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
} }
if !user.IsPending() { if !user.IsPending() {
return errors.New("user is not pending") ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("user is not pending"))
return
} }
if err := user.SetRole(dbModel.RoleUser); err != nil {
return err err = db.SetRoleByID(req.ID, dbModel.RoleUser)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
} }
return nil
ctx.Status(http.StatusNoContent)
} }
func BanUser(ctx *gin.Context) { func BanUser(ctx *gin.Context) {
@ -197,7 +210,15 @@ func BanUser(ctx *gin.Context) {
u, err := op.GetUserById(req.ID) u, err := op.GetUserById(req.ID)
if err != nil { if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) if errors.Is(err, op.ErrUserPending) {
err = db.SetRoleByID(req.ID, dbModel.RoleBanned)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
} else {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
}
return return
} }
@ -218,3 +239,149 @@ func BanUser(ctx *gin.Context) {
ctx.Status(http.StatusNoContent) ctx.Status(http.StatusNoContent)
} }
func PendingRooms(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
}
page, pageSize, err := GetPageAndPageSize(ctx)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
var desc = ctx.DefaultQuery("sort", "desc") == "desc"
scopes := []func(db *gorm.DB) *gorm.DB{
db.WhereStatus(dbModel.RoomStatusPending),
}
if keyword := ctx.Query("keyword"); keyword != "" {
scopes = append(scopes, db.WhereRoomNameLike(keyword))
}
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("name"))
} else {
scopes = append(scopes, db.OrderByAsc("name"))
}
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 keyword := ctx.Query("keyword"); keyword != "" {
// search mode, all, name, creator
switch ctx.DefaultQuery("search", "all") {
case "all":
scopes = append(scopes, db.WhereRoomNameLikeOrCreatorIn(keyword, db.GerUsersIDByUsernameLike(keyword)))
case "name":
scopes = append(scopes, db.WhereRoomNameLike(keyword))
case "creator":
scopes = append(scopes, db.WhereCreatorIDIn(db.GerUsersIDByUsernameLike(keyword)))
}
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": db.GetAllRoomsWithoutHiddenCount(scopes...),
"list": genRoomListResp(append(scopes, db.Paginate(page, pageSize))...),
}))
}
func ApprovePendingRoom(ctx *gin.Context) {
req := model.RoomIDReq{}
if err := model.Decode(ctx, &req); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
room, err := db.GetRoomByID(req.Id)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if !room.IsPending() {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("room is not pending"))
return
}
err = db.SetRoomStatus(req.Id, dbModel.RoomStatusActive)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Status(http.StatusNoContent)
}
func BanRoom(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.User)
req := model.RoomIDReq{}
if err := model.Decode(ctx, &req); err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
room, err := op.LoadOrInitRoomByID(req.Id)
if err != nil {
if errors.Is(err, op.ErrRoomPending) || errors.Is(err, op.ErrRoomStopped) {
err = db.SetRoomStatus(req.Id, dbModel.RoomStatusBanned)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
} else {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
}
return
}
creator, err := db.GetUserByID(room.CreatorID)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
if creator.ID == user.ID {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot ban yourself"))
return
}
if creator.IsAdmin() {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("no permission"))
return
}
if room.IsBanned() {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("room is already banned"))
return
}
err = room.SetRoomStatus(dbModel.RoomStatusBanned)
if err != nil {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Status(http.StatusNoContent)
}

@ -50,6 +50,18 @@ func Init(e *gin.Engine) {
admin.POST("/settings", EditAdminSettings) admin.POST("/settings", EditAdminSettings)
admin.GET("/users", Users) admin.GET("/users", Users)
admin.GET("/pending/users", PendingUsers)
admin.GET("/pending/rooms", PendingRooms)
admin.POST("/approve/user", ApprovePendingUser)
admin.POST("/approve/room", ApprovePendingRoom)
admin.POST("/ban/user", BanUser)
admin.POST("/ban/room", BanRoom)
} }
{ {
@ -57,7 +69,7 @@ func Init(e *gin.Engine) {
root.POST("/addAdmin", AddAdmin) root.POST("/addAdmin", AddAdmin)
root.POST("deleteAdmin", DeleteAdmin) root.POST("/deleteAdmin", DeleteAdmin)
} }
} }

@ -7,6 +7,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/synctv-org/synctv/internal/db" "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/op"
"github.com/synctv-org/synctv/internal/settings" "github.com/synctv-org/synctv/internal/settings"
"github.com/synctv-org/synctv/server/middlewares" "github.com/synctv-org/synctv/server/middlewares"
@ -91,10 +92,9 @@ func RoomList(ctx *gin.Context) {
var desc = ctx.DefaultQuery("sort", "desc") == "desc" var desc = ctx.DefaultQuery("sort", "desc") == "desc"
// search mode, all, name, creator scopes := []func(db *gorm.DB) *gorm.DB{
var search = ctx.DefaultQuery("search", "all") db.WhereStatus(dbModel.RoomStatusActive),
}
scopes := []func(db *gorm.DB) *gorm.DB{}
switch order { switch order {
case "createdAt": case "createdAt":
@ -103,53 +103,35 @@ func RoomList(ctx *gin.Context) {
} else { } else {
scopes = append(scopes, db.OrderByCreatedAtAsc) scopes = append(scopes, db.OrderByCreatedAtAsc)
} }
if keyword := ctx.Query("keyword"); keyword != "" {
switch search {
case "all":
scopes = append(scopes, db.WhereRoomNameLikeOrCreatorIn(keyword, db.GerUsersIDByUsernameLike(keyword)))
case "name":
scopes = append(scopes, db.WhereRoomNameLike(keyword))
case "creator":
scopes = append(scopes, db.WhereCreatorIDIn(db.GerUsersIDByUsernameLike(keyword)))
}
}
case "roomName": case "roomName":
if desc { if desc {
scopes = append(scopes, db.OrderByDesc("name")) scopes = append(scopes, db.OrderByDesc("name"))
} else { } else {
scopes = append(scopes, db.OrderByAsc("name")) scopes = append(scopes, db.OrderByAsc("name"))
} }
if keyword := ctx.Query("keyword"); keyword != "" {
switch search {
case "all":
scopes = append(scopes, db.WhereRoomNameLikeOrCreatorIn(keyword, db.GerUsersIDByUsernameLike(keyword)))
case "name":
scopes = append(scopes, db.WhereRoomNameLike(keyword))
case "creator":
scopes = append(scopes, db.WhereCreatorIDIn(db.GerUsersIDByUsernameLike(keyword)))
}
}
case "roomId": case "roomId":
if desc { if desc {
scopes = append(scopes, db.OrderByIDDesc) scopes = append(scopes, db.OrderByIDDesc)
} else { } else {
scopes = append(scopes, db.OrderByIDAsc) scopes = append(scopes, db.OrderByIDAsc)
} }
if keyword := ctx.Query("keyword"); keyword != "" {
switch search {
case "all":
scopes = append(scopes, db.WhereRoomNameLikeOrCreatorIn(keyword, db.GerUsersIDByUsernameLike(keyword)))
case "name":
scopes = append(scopes, db.WhereRoomNameLike(keyword))
case "creator":
scopes = append(scopes, db.WhereCreatorIDIn(db.GerUsersIDByUsernameLike(keyword)))
}
}
default: default:
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support order")) ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support order"))
return return
} }
if keyword := ctx.Query("keyword"); keyword != "" {
// search mode, all, name, creator
switch ctx.DefaultQuery("search", "all") {
case "all":
scopes = append(scopes, db.WhereRoomNameLikeOrCreatorIn(keyword, db.GerUsersIDByUsernameLike(keyword)))
case "name":
scopes = append(scopes, db.WhereRoomNameLike(keyword))
case "creator":
scopes = append(scopes, db.WhereCreatorIDIn(db.GerUsersIDByUsernameLike(keyword)))
}
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": db.GetAllRoomsWithoutHiddenCount(scopes...), "total": db.GetAllRoomsWithoutHiddenCount(scopes...),
"list": genRoomListResp(append(scopes, db.Paginate(page, pageSize))...), "list": genRoomListResp(append(scopes, db.Paginate(page, pageSize))...),

@ -44,7 +44,7 @@ func AddAdmin(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("user not found")) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("user not found"))
return return
} }
if u.Role >= dbModel.RoleAdmin { if u.IsAdmin() {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("user is already admin")) ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("user is already admin"))
return return
} }
@ -75,7 +75,7 @@ func DeleteAdmin(ctx *gin.Context) {
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("user not found")) ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorStringResp("user not found"))
return return
} }
if u.Role == dbModel.RoleRoot { if u.IsRoot() {
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot remove root")) ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("cannot remove root"))
return return
} }

@ -76,12 +76,6 @@ 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.IsBanned() {
return nil, nil, errors.New("user banned")
}
if u.IsPending() {
return nil, nil, errors.New("user is pending, need admin to approve")
}
r, err := op.LoadOrInitRoomByID(claims.RoomId) r, err := op.LoadOrInitRoomByID(claims.RoomId)
if err != nil { if err != nil {
@ -108,12 +102,6 @@ func AuthUser(Authorization string) (*op.User, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
if u.IsBanned() {
return nil, errors.New("user banned")
}
if u.IsPending() {
return nil, errors.New("user is pending, need admin to approve")
}
return u, nil return u, nil
} }

@ -107,17 +107,18 @@ func (s *SetRoomPasswordReq) Validate() error {
return nil return nil
} }
type UserIdReq struct { type RoomIDReq struct {
UserId string `json:"userId"` Id string `json:"id"`
} }
func (u *UserIdReq) Decode(ctx *gin.Context) error { func (r *RoomIDReq) Decode(ctx *gin.Context) error {
return json.NewDecoder(ctx.Request.Body).Decode(u) return json.NewDecoder(ctx.Request.Body).Decode(r)
} }
func (u *UserIdReq) Validate() error { func (r *RoomIDReq) Validate() error {
if len(u.UserId) != 36 { if len(r.Id) != 36 {
return ErrEmptyUserId return ErrEmptyRoomName
} }
return nil return nil
} }

Loading…
Cancel
Save