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)
}
}
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)
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 {
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 {
return u.Role == RoleAdmin
return u.Role == RoleAdmin || u.IsRoot()
}
func (u *User) IsPending() bool {
return u.Role == RolePending
}
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 {
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
}
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) {
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())
i, loaded := roomCache.LoadOrStore(room.ID, &Room{
Room: *room,
@ -52,14 +66,6 @@ func LoadOrInitRoom(room *model.Room) (*Room, error) {
if loaded {
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
}
@ -79,6 +85,19 @@ func CloseRoom(roomID string) error {
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) {
r2, loaded := roomCache.Load(id)
if loaded {

@ -13,6 +13,11 @@ import (
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) {
i, err := userCache.Get(id)
if err == nil {
@ -24,6 +29,13 @@ func GetUserById(id string) (*User, error) {
return nil, err
}
switch u.Role {
case model.RoleBanned:
return nil, ErrUserBanned
case model.RolePending:
return nil, ErrUserPending
}
u2 := &User{
User: *u,
}

@ -172,18 +172,31 @@ func PendingUsers(ctx *gin.Context) {
}))
}
func ApprovePendingUser(Authorization, userID string) error {
user, err := op.GetUserById(userID)
func ApprovePendingUser(ctx *gin.Context) {
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 {
return err
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
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) {
@ -197,7 +210,15 @@ func BanUser(ctx *gin.Context) {
u, err := op.GetUserById(req.ID)
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
}
@ -218,3 +239,149 @@ func BanUser(ctx *gin.Context) {
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.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("deleteAdmin", DeleteAdmin)
root.POST("/deleteAdmin", DeleteAdmin)
}
}

@ -7,6 +7,7 @@ import (
"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/middlewares"
@ -91,10 +92,9 @@ func RoomList(ctx *gin.Context) {
var desc = ctx.DefaultQuery("sort", "desc") == "desc"
// search mode, all, name, creator
var search = ctx.DefaultQuery("search", "all")
scopes := []func(db *gorm.DB) *gorm.DB{}
scopes := []func(db *gorm.DB) *gorm.DB{
db.WhereStatus(dbModel.RoomStatusActive),
}
switch order {
case "createdAt":
@ -103,53 +103,35 @@ func RoomList(ctx *gin.Context) {
} else {
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":
if desc {
scopes = append(scopes, db.OrderByDesc("name"))
} else {
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":
if desc {
scopes = append(scopes, db.OrderByIDDesc)
} else {
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:
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))...),

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

@ -76,12 +76,6 @@ func AuthRoom(Authorization string) (*op.User, *op.Room, error) {
if err != nil {
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)
if err != nil {
@ -108,12 +102,6 @@ func AuthUser(Authorization string) (*op.User, error) {
if err != nil {
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
}

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

Loading…
Cancel
Save