package handlers import ( "errors" "fmt" "net/http" "time" "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" "github.com/synctv-org/synctv/server/model" "github.com/synctv-org/synctv/utils" "github.com/zijiren233/gencontainer/refreshcache" "gorm.io/gorm" ) var ( ErrAuthFailed = errors.New("auth failed") ErrAuthExpired = errors.New("auth expired") ErrRoomAlready = errors.New("room already exists") ) type FormatErrNotSupportPosition string func (e FormatErrNotSupportPosition) Error() string { return fmt.Sprintf("not support position %s", string(e)) } func CreateRoom(ctx *gin.Context) { user := ctx.MustGet("user").(*op.User) if settings.DisableCreateRoom.Get() && !user.IsAdmin() { ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("create room is disabled")) return } req := model.CreateRoomReq{} if err := model.Decode(ctx, &req); err != nil { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } r, err := user.CreateRoom(req.RoomName, req.Password, db.WithSetting(req.Setting)) if err != nil { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } room, _ := op.LoadOrInitRoomByID(r.ID) token, err := middlewares.NewAuthRoomToken(user, room) if err != nil { ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } ctx.JSON(http.StatusCreated, model.NewApiDataResp(gin.H{ "roomId": room.ID, "token": token, })) } var roomHotCache = refreshcache.NewRefreshCache[[]*op.RoomInfo](func() ([]*op.RoomInfo, error) { return op.GetRoomHeapInCacheWithoutHidden(), nil }, time.Second*3) func RoomHotList(ctx *gin.Context) { page, pageSize, err := GetPageAndPageSize(ctx) if err != nil { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } r, _ := roomHotCache.Get() rs := utils.GetPageItems(r, page, pageSize) ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "total": len(r), "list": rs, })) } func RoomList(ctx *gin.Context) { 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.WhereRoomSettingWithoutHidden(), db.WhereStatus(dbModel.RoomStatusActive), } switch ctx.DefaultQuery("order", "name") { 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")) } 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.WhereRoomNameLikeOrCreatorInOrIDLike(keyword, db.GerUsersIDByUsernameLike(keyword), keyword)) case "name": scopes = append(scopes, db.WhereRoomNameLike(keyword)) case "creator": scopes = append(scopes, db.WhereCreatorIDIn(db.GerUsersIDByUsernameLike(keyword))) case "id": scopes = append(scopes, db.WhereIDLike(keyword)) } } ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "total": db.GetAllRoomsCount(scopes...), "list": genRoomListResp(append(scopes, db.Paginate(page, pageSize))...), })) } func genRoomListResp(scopes ...func(db *gorm.DB) *gorm.DB) []*model.RoomListResp { rs := db.GetAllRooms(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(), Status: r.Status, } } return resp } func CheckRoom(ctx *gin.Context) { r, err := db.GetRoomByID(ctx.Query("roomId")) if err != nil { ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err)) return } ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "peopleNum": op.ClientNum(r.ID), "needPassword": r.NeedPassword(), "creator": op.GetUserName(r.CreatorID), })) } func LoginRoom(ctx *gin.Context) { user := ctx.MustGet("user").(*op.User) req := model.LoginRoomReq{} if err := model.Decode(ctx, &req); err != nil { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } room, err := op.LoadOrInitRoomByID(req.RoomId) if err != nil { ctx.AbortWithStatusJSON(http.StatusNotFound, model.NewApiErrorResp(err)) return } if room.CreatorID != user.ID && !room.CheckPassword(req.Password) { ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("password error")) return } token, err := middlewares.NewAuthRoomToken(user, room) if err != nil { ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "roomId": room.ID, "token": token, })) } func DeleteRoom(ctx *gin.Context) { room := ctx.MustGet("room").(*op.Room) user := ctx.MustGet("user").(*op.User) if err := user.DeleteRoom(room); err != nil { ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err)) return } ctx.Status(http.StatusNoContent) } func SetRoomPassword(ctx *gin.Context) { room := ctx.MustGet("room").(*op.Room) user := ctx.MustGet("user").(*op.User) req := model.SetRoomPasswordReq{} if err := model.Decode(ctx, &req); err != nil { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } if err := user.SetRoomPassword(room, req.Password); err != nil { ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err)) return } token, err := middlewares.NewAuthRoomToken(user, room) if err != nil { ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err)) return } ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ "roomId": room.ID, "token": token, })) } func RoomSetting(ctx *gin.Context) { room := ctx.MustGet("room").(*op.Room) // user := ctx.MustGet("user").(*op.User) ctx.JSON(http.StatusOK, model.NewApiDataResp(room.Settings)) } func SetRoomSetting(ctx *gin.Context) { room := ctx.MustGet("room").(*op.Room) user := ctx.MustGet("user").(*op.User) req := model.SetRoomSettingReq{} if err := model.Decode(ctx, &req); err != nil { ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err)) return } if err := user.SetRoomSetting(room, dbModel.RoomSettings(req)); err != nil { ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err)) return } ctx.Status(http.StatusNoContent) } // func RoomUsers(ctx *gin.Context) { // room := ctx.MustGet("room").(*op.Room) // 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{} // roomUserRelationScopes := []func(db *gorm.DB) *gorm.DB{} // switch ctx.DefaultQuery("status", "active") { // case "pending": // roomUserRelationScopes = append(roomUserRelationScopes, db.WhereRoomUserStatus(dbModel.RoomUserStatusPending)) // case "banned": // roomUserRelationScopes = append(roomUserRelationScopes, db.WhereRoomUserStatus(dbModel.RoomUserStatusBanned)) // case "active": // roomUserRelationScopes = append(roomUserRelationScopes, db.WhereRoomUserStatus(dbModel.RoomUserStatusActive)) // default: // ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support role")) // return // } // switch ctx.DefaultQuery("order", "name") { // case "join": // if desc { // roomUserRelationScopes = append(roomUserRelationScopes, db.OrderByCreatedAtDesc) // } else { // roomUserRelationScopes = append(roomUserRelationScopes, db.OrderByCreatedAtAsc) // } // case "name": // if desc { // scopes = append(scopes, db.OrderByDesc("username")) // } else { // scopes = append(scopes, db.OrderByAsc("username")) // } // default: // ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support order")) // return // } // if keyword := ctx.Query("keyword"); keyword != "" { // // search mode, all, name, id // switch ctx.DefaultQuery("search", "all") { // case "all": // scopes = append(scopes, db.WhereUsernameLikeOrIDIn(keyword, db.GerUsersIDByIDLike(keyword))) // case "name": // scopes = append(scopes, db.WhereUsernameLike(keyword)) // case "id": // scopes = append(scopes, db.WhereIDIn(db.GerUsersIDByIDLike(keyword))) // } // } // ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{ // "total": db.GetAllRoomUsersCount(room.ID, roomUserRelationScopes, scopes...), // "list": genUserListResp(db.GetAllRoomUsers(room.ID, roomUserRelationScopes, append(scopes, db.Paginate(page, pageSize))...)), // })) // }