Feat: init permission control

pull/134/head
zijiren233 2 years ago
parent 2f604e2132
commit 9b0b9ff1f2

@ -20,7 +20,10 @@ var ShowCmd = &cobra.Command{
).Run()
},
RunE: func(cmd *cobra.Command, args []string) error {
admins := db.GetAdmins()
admins, err := db.GetAdmins()
if err != nil {
fmt.Printf("get admins failed: %s\n", err.Error())
}
for _, admin := range admins {
fmt.Printf("id: %s\tusername: %s\n", admin.ID, admin.Username)
}

@ -31,7 +31,7 @@ func InitDatabase(ctx context.Context) (err error) {
Logger: newDBLogger(),
PrepareStmt: true,
DisableForeignKeyConstraintWhenMigrating: false,
IgnoreRelationshipsWhenMigrating: true,
IgnoreRelationshipsWhenMigrating: false,
})
d, err := gorm.Open(dialector, opts...)
if err != nil {

@ -109,9 +109,9 @@ func WhereRoomID(roomID string) func(db *gorm.DB) *gorm.DB {
}
}
func PreloadRoomUserRelations(scopes ...func(*gorm.DB) *gorm.DB) func(db *gorm.DB) *gorm.DB {
func PreloadRoomMembers(scopes ...func(*gorm.DB) *gorm.DB) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Preload("RoomUserRelations", func(db *gorm.DB) *gorm.DB {
return db.Preload("RoomMembers", func(db *gorm.DB) *gorm.DB {
return db.Scopes(scopes...)
})
}
@ -243,13 +243,7 @@ func WhereIDIn(ids []string) func(db *gorm.DB) *gorm.DB {
func WhereRoomSettingWithoutHidden() func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("settings_hidden = ?", false)
}
}
func WhereRoomSettingHidden() func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("settings_hidden = ?", true)
return db.Where("hidden = ?", false)
}
}
@ -264,7 +258,7 @@ func WhereIDLike(id string) func(db *gorm.DB) *gorm.DB {
}
}
func WhereRoomUserStatus(status model.RoomUserStatus) func(db *gorm.DB) *gorm.DB {
func WhereRoomMemberStatus(status model.RoomMemberStatus) func(db *gorm.DB) *gorm.DB {
return func(db *gorm.DB) *gorm.DB {
return db.Where("status = ?", status)
}

@ -0,0 +1,133 @@
package db
import (
"fmt"
"github.com/synctv-org/synctv/internal/model"
"gorm.io/gorm"
)
type CreateRoomMemberRelationConfig func(r *model.RoomMember)
func WithRoomMemberStatus(status model.RoomMemberStatus) CreateRoomMemberRelationConfig {
return func(r *model.RoomMember) {
r.Status = status
}
}
func WithRoomMemberRole(role model.RoomMemberRole) CreateRoomMemberRelationConfig {
return func(r *model.RoomMember) {
r.Role = role
}
}
func WithRoomMemberRelationPermissions(permissions model.RoomMemberPermission) CreateRoomMemberRelationConfig {
return func(r *model.RoomMember) {
r.Permissions = permissions
}
}
func WithRoomMemberAdminPermissions(permissions model.RoomAdminPermission) CreateRoomMemberRelationConfig {
return func(r *model.RoomMember) {
r.AdminPermissions = permissions
}
}
func FirstOrCreateRoomMemberRelation(roomID, userID string, conf ...CreateRoomMemberRelationConfig) (*model.RoomMember, error) {
roomMemberRelation := &model.RoomMember{}
d := &model.RoomMember{
RoomID: roomID,
UserID: userID,
Role: model.RoomMemberRoleMember,
Status: model.RoomMemberStatusPending,
Permissions: model.NoPermission,
AdminPermissions: model.NoAdminPermission,
}
for _, c := range conf {
c(d)
}
err := db.Where("room_id = ? AND user_id = ?", roomID, userID).Attrs(d).FirstOrCreate(roomMemberRelation).Error
return roomMemberRelation, err
}
func GetRoomMemberRelation(roomID, userID string) (*model.RoomMember, error) {
roomMemberRelation := &model.RoomMember{}
err := db.Where("room_id = ? AND user_id = ?", roomID, userID).First(roomMemberRelation).Error
return roomMemberRelation, HandleNotFound(err, "room or user")
}
func RoomApprovePendingMember(roomID, userID string) error {
roomMember := &model.RoomMember{}
err := db.Where("room_id = ? AND user_id = ?", roomID, userID).First(roomMember).Error
if err != nil {
return err
}
if roomMember.Status != model.RoomMemberStatusPending {
return fmt.Errorf("user is not pending")
}
err = db.Model(&model.RoomMember{}).Where("room_id = ? AND user_id = ? AND status = ?", roomID, userID, model.RoomMemberStatusPending).Update("status", model.RoomMemberStatusActive).Error
if err != nil && gorm.ErrRecordNotFound != err {
return fmt.Errorf("update status failed")
}
return err
}
func RoomBanMember(roomID, userID string) error {
err := db.Model(&model.RoomMember{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("status", model.RoomMemberStatusBanned).Error
return HandleNotFound(err, "room or user")
}
func RoomUnbanMember(roomID, userID string) error {
err := db.Model(&model.RoomMember{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("status", model.RoomMemberStatusActive).Error
return HandleNotFound(err, "room or user")
}
func SetMemberPermissions(roomID string, userID string, permission model.RoomMemberPermission) error {
err := db.Model(&model.RoomMember{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", permission).Error
return HandleNotFound(err, "room or user")
}
func AddMemberPermissions(roomID string, userID string, permission model.RoomMemberPermission) error {
err := db.Model(&model.RoomMember{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", db.Raw("permissions | ?", permission)).Error
return HandleNotFound(err, "room or user")
}
func RemoveMemberPermissions(roomID string, userID string, permission model.RoomMemberPermission) error {
err := db.Model(&model.RoomMember{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", db.Raw("permissions & ?", ^permission)).Error
return HandleNotFound(err, "room or user")
}
// func GetAllRoomMembersRelationCount(roomID string, scopes ...func(*gorm.DB) *gorm.DB) (int64, error) {
// var count int64
// err := db.Model(&model.RoomMember{}).Where("room_id = ?", roomID).Scopes(scopes...).Count(&count).Error
// return count, err
// }
func RoomSetAdminPermissions(roomID, userID string, permissions model.RoomAdminPermission) error {
err := db.Model(&model.RoomMember{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("admin_permissions", permissions).Error
return HandleNotFound(err, "room or user")
}
func RoomAddAdminPermissions(roomID, userID string, permissions model.RoomAdminPermission) error {
err := db.Model(&model.RoomMember{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("admin_permissions", db.Raw("admin_permissions | ?", permissions)).Error
return HandleNotFound(err, "room or user")
}
func RoomRemoveAdminPermissions(roomID, userID string, permissions model.RoomAdminPermission) error {
err := db.Model(&model.RoomMember{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("admin_permissions", db.Raw("admin_permissions & ?", ^permissions)).Error
return HandleNotFound(err, "room or user")
}
func RoomSetAdmin(roomID, userID string, permissions model.RoomAdminPermission) error {
return db.Model(&model.RoomMember{}).Where("room_id = ? AND user_id = ?", roomID, userID).Updates(map[string]interface{}{
"role": model.RoomMemberRoleAdmin,
"admin_permissions": permissions,
}).Error
}
func RoomSetMember(roomID, userID string, permissions model.RoomMemberPermission) error {
return db.Model(&model.RoomMember{}).Where("room_id = ? AND user_id = ?", roomID, userID).Updates(map[string]interface{}{
"role": model.RoomMemberRoleMember,
"permissions": permissions,
}).Error
}

@ -1,72 +0,0 @@
package db
import (
"github.com/synctv-org/synctv/internal/model"
"gorm.io/gorm"
)
type CreateRoomUserRelationConfig func(r *model.RoomUserRelation)
func WithRoomUserRelationStatus(status model.RoomUserStatus) CreateRoomUserRelationConfig {
return func(r *model.RoomUserRelation) {
r.Status = status
}
}
func WithRoomUserRelationPermissions(permissions model.RoomUserPermission) CreateRoomUserRelationConfig {
return func(r *model.RoomUserRelation) {
r.Permissions = permissions
}
}
func FirstOrCreateRoomUserRelation(roomID, userID string, conf ...CreateRoomUserRelationConfig) (*model.RoomUserRelation, error) {
roomUserRelation := &model.RoomUserRelation{}
d := &model.RoomUserRelation{
RoomID: roomID,
UserID: userID,
Permissions: model.DefaultPermissions,
}
for _, c := range conf {
c(d)
}
err := db.Where("room_id = ? AND user_id = ?", roomID, userID).Attrs(d).FirstOrCreate(roomUserRelation).Error
return roomUserRelation, err
}
func GetRoomUserRelation(roomID, userID string) (*model.RoomUserRelation, error) {
roomUserRelation := &model.RoomUserRelation{}
err := db.Where("room_id = ? AND user_id = ?", roomID, userID).First(roomUserRelation).Error
return roomUserRelation, HandleNotFound(err, "room or user")
}
func SetRoomUserStatus(roomID string, userID string, status model.RoomUserStatus) error {
err := db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("status", status).Error
return HandleNotFound(err, "room or user")
}
func SetUserPermission(roomID string, userID string, permission model.RoomUserPermission) error {
err := db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", permission).Error
return HandleNotFound(err, "room or user")
}
func AddUserPermission(roomID string, userID string, permission model.RoomUserPermission) error {
err := db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", db.Raw("permissions | ?", permission)).Error
return HandleNotFound(err, "room or user")
}
func RemoveUserPermission(roomID string, userID string, permission model.RoomUserPermission) error {
err := db.Model(&model.RoomUserRelation{}).Where("room_id = ? AND user_id = ?", roomID, userID).Update("permissions", db.Raw("permissions & ?", ^permission)).Error
return HandleNotFound(err, "room or user")
}
func GetAllRoomUsersRelation(roomID string, scopes ...func(*gorm.DB) *gorm.DB) []*model.RoomUserRelation {
var roomUserRelations []*model.RoomUserRelation
db.Where("room_id = ?", roomID).Scopes(scopes...).Find(&roomUserRelations)
return roomUserRelations
}
func GetAllRoomUsersRelationCount(roomID string, scopes ...func(*gorm.DB) *gorm.DB) int64 {
var count int64
db.Model(&model.RoomUserRelation{}).Where("room_id = ?", roomID).Scopes(scopes...).Count(&count)
return count
}

@ -12,7 +12,7 @@ import (
type CreateRoomConfig func(r *model.Room)
func WithSetting(setting model.RoomSettings) CreateRoomConfig {
func WithSetting(setting *model.RoomSettings) CreateRoomConfig {
return func(r *model.Room) {
r.Settings = setting
}
@ -21,17 +21,19 @@ func WithSetting(setting model.RoomSettings) CreateRoomConfig {
func WithCreator(creator *model.User) CreateRoomConfig {
return func(r *model.Room) {
r.CreatorID = creator.ID
r.GroupUserRelations = []*model.RoomUserRelation{
r.GroupUserRelations = []*model.RoomMember{
{
UserID: creator.ID,
Status: model.RoomUserStatusActive,
Permissions: model.PermissionAll,
UserID: creator.ID,
Status: model.RoomMemberStatusActive,
Role: model.RoomMemberRoleCreator,
Permissions: model.AllPermissions,
AdminPermissions: model.AllAdminPermissions,
},
}
}
}
func WithRelations(relations []*model.RoomUserRelation) CreateRoomConfig {
func WithRelations(relations []*model.RoomMember) CreateRoomConfig {
return func(r *model.Room) {
r.GroupUserRelations = append(r.GroupUserRelations, relations...)
}
@ -43,10 +45,17 @@ func WithStatus(status model.RoomStatus) CreateRoomConfig {
}
}
func WithSettingHidden(hidden bool) CreateRoomConfig {
return func(r *model.Room) {
r.Settings.Hidden = hidden
}
}
// if maxCount is 0, it will be ignored
func CreateRoom(name, password string, maxCount int64, conf ...CreateRoomConfig) (*model.Room, error) {
r := &model.Room{
Name: name,
Name: name,
Settings: model.DefaultRoomSettings(),
}
for _, c := range conf {
c(r)
@ -83,15 +92,29 @@ func GetRoomByID(id string) (*model.Room, error) {
return nil, errors.New("room id is not 32 bit")
}
r := &model.Room{}
err := db.Where("id = ?", id).First(r).Error
err := db.
Where("id = ?", id).
Preload("Settings", "id = ?", id).
First(r).Error
return r, HandleNotFound(err, "room")
}
func SaveRoomSettings(roomID string, setting model.RoomSettings) error {
err := db.Model(&model.Room{}).Where("id = ?", roomID).Update("setting", setting).Error
func SaveRoomSettings(roomID string, settings *model.RoomSettings) error {
settings.ID = roomID
err := db.Save(settings).Error
return HandleNotFound(err, "room")
}
func UpdateRoomSettings(roomID string, settings map[string]interface{}) (*model.RoomSettings, error) {
rs := &model.RoomSettings{
ID: roomID,
}
err := db.Model(rs).
Clauses(clause.Returning{}).
Updates(settings).Error
return rs, HandleNotFound(err, "room")
}
func DeleteRoomByID(roomID string) error {
err := db.Unscoped().Select(clause.Associations).Delete(&model.Room{ID: roomID}).Error
return HandleNotFound(err, "room")
@ -114,28 +137,28 @@ func SetRoomHashedPassword(roomID string, hashedPassword []byte) error {
return HandleNotFound(err, "room")
}
func GetAllRooms(scopes ...func(*gorm.DB) *gorm.DB) []*model.Room {
func GetAllRooms(scopes ...func(*gorm.DB) *gorm.DB) ([]*model.Room, error) {
rooms := []*model.Room{}
db.Scopes(scopes...).Find(&rooms)
return rooms
err := db.Scopes(scopes...).Find(&rooms).Error
return rooms, err
}
func GetAllRoomsCount(scopes ...func(*gorm.DB) *gorm.DB) int64 {
func GetAllRoomsCount(scopes ...func(*gorm.DB) *gorm.DB) (int64, error) {
var count int64
db.Model(&model.Room{}).Scopes(scopes...).Count(&count)
return count
err := db.Model(&model.Room{}).Scopes(scopes...).Count(&count).Error
return count, err
}
func GetAllRoomsAndCreator(scopes ...func(*gorm.DB) *gorm.DB) []*model.Room {
func GetAllRoomsAndCreator(scopes ...func(*gorm.DB) *gorm.DB) ([]*model.Room, error) {
rooms := []*model.Room{}
db.Preload("Creator").Scopes(scopes...).Find(&rooms)
return rooms
err := db.Preload("Creator").Scopes(scopes...).Find(&rooms).Error
return rooms, err
}
func GetAllRoomsByUserID(userID string) []*model.Room {
func GetAllRoomsByUserID(userID string) ([]*model.Room, error) {
rooms := []*model.Room{}
db.Where("creator_id = ?", userID).Find(&rooms)
return rooms
err := db.Where("creator_id = ?", userID).Find(&rooms).Error
return rooms, err
}
func SetRoomStatus(roomID string, status model.RoomStatus) error {

@ -22,7 +22,8 @@ var models = []any{
new(model.User),
new(model.UserProvider),
new(model.Room),
new(model.RoomUserRelation),
new(model.RoomSettings),
new(model.RoomMember),
new(model.Movie),
new(model.BilibiliVendor),
new(model.AlistVendor),

@ -236,22 +236,22 @@ func GetUserByUsername(username string) (*model.User, error) {
return u, HandleNotFound(err, "user")
}
func GetUserByUsernameLike(username string, scopes ...func(*gorm.DB) *gorm.DB) []*model.User {
func GetUserByUsernameLike(username string, scopes ...func(*gorm.DB) *gorm.DB) ([]*model.User, error) {
var users []*model.User
db.Where(`username LIKE ?`, fmt.Sprintf("%%%s%%", username)).Scopes(scopes...).Find(&users)
return users
err := db.Where(`username LIKE ?`, fmt.Sprintf("%%%s%%", username)).Scopes(scopes...).Find(&users).Error
return users, err
}
func GerUsersIDByUsernameLike(username string, scopes ...func(*gorm.DB) *gorm.DB) []string {
func GerUsersIDByUsernameLike(username string, scopes ...func(*gorm.DB) *gorm.DB) ([]string, error) {
var ids []string
db.Model(&model.User{}).Where(`username LIKE ?`, fmt.Sprintf("%%%s%%", username)).Scopes(scopes...).Pluck("id", &ids)
return ids
err := db.Model(&model.User{}).Where(`username LIKE ?`, fmt.Sprintf("%%%s%%", username)).Scopes(scopes...).Pluck("id", &ids).Error
return ids, err
}
func GerUsersIDByIDLike(id string, scopes ...func(*gorm.DB) *gorm.DB) []string {
func GerUsersIDByIDLike(id string, scopes ...func(*gorm.DB) *gorm.DB) ([]string, error) {
var ids []string
db.Model(&model.User{}).Where(`id LIKE ?`, utils.LIKE(id)).Scopes(scopes...).Pluck("id", &ids)
return ids
err := db.Model(&model.User{}).Where(`id LIKE ?`, utils.LIKE(id)).Scopes(scopes...).Pluck("id", &ids).Error
return ids, err
}
func GetUserByIDOrUsernameLike(idOrUsername string, scopes ...func(*gorm.DB) *gorm.DB) ([]*model.User, error) {
@ -332,10 +332,10 @@ func RemoveAdmin(u *model.User) error {
return SaveUser(u)
}
func GetAdmins() []*model.User {
func GetAdmins() ([]*model.User, error) {
var users []*model.User
db.Where("role == ?", model.RoleAdmin).Find(&users)
return users
err := db.Where("role == ?", model.RoleAdmin).Find(&users).Error
return users, err
}
func AddAdminByID(userID string) error {
@ -395,16 +395,16 @@ func SetUsernameByID(userID string, username string) error {
return HandleNotFound(err, "user")
}
func GetAllUserCount(scopes ...func(*gorm.DB) *gorm.DB) int64 {
func GetAllUserCount(scopes ...func(*gorm.DB) *gorm.DB) (int64, error) {
var count int64
db.Model(&model.User{}).Scopes(scopes...).Count(&count)
return count
err := db.Model(&model.User{}).Scopes(scopes...).Count(&count).Error
return count, err
}
func GetAllUsers(scopes ...func(*gorm.DB) *gorm.DB) []*model.User {
func GetAllUsers(scopes ...func(*gorm.DB) *gorm.DB) ([]*model.User, error) {
var users []*model.User
db.Scopes(scopes...).Find(&users)
return users
err := db.Scopes(scopes...).Find(&users).Error
return users, err
}
func SetUserHashedPassword(id string, hashedPassword []byte) error {

@ -0,0 +1,173 @@
package model
import (
"errors"
"math"
"time"
)
type RoomMemberStatus uint64
const (
RoomMemberStatusUnknown RoomMemberStatus = iota
RoomMemberStatusBanned
RoomMemberStatusPending
RoomMemberStatusActive
)
func (r RoomMemberStatus) String() string {
switch r {
case RoomMemberStatusBanned:
return "banned"
case RoomMemberStatusPending:
return "pending"
case RoomMemberStatusActive:
return "active"
default:
return "unknown"
}
}
func (r RoomMemberStatus) IsPending() bool {
return r == RoomMemberStatusPending
}
func (r RoomMemberStatus) IsActive() bool {
return r == RoomMemberStatusActive
}
func (r RoomMemberStatus) IsBanned() bool {
return r == RoomMemberStatusBanned
}
type RoomMemberPermission uint32
const (
PermissionGetMovieList RoomMemberPermission = 2 << iota
PermissionAddMovie
PermissionDeleteMovie
PermissionEditMovie
PermissionSetCurrentMovie
PermissionSetCurrentStatus
PermissionSendChatMessage
AllPermissions RoomMemberPermission = math.MaxUint32
NoPermission RoomMemberPermission = 0
DefaultPermissions RoomMemberPermission = PermissionGetMovieList | PermissionSendChatMessage
)
func (p RoomMemberPermission) RemoveAdmin() RoomMemberPermission {
return p & DefaultPermissions
}
func (p RoomMemberPermission) Has(permission RoomMemberPermission) bool {
return p&permission == permission
}
func (p RoomMemberPermission) Add(permission RoomMemberPermission) RoomMemberPermission {
return p | permission
}
func (p RoomMemberPermission) Remove(permission RoomMemberPermission) RoomMemberPermission {
return p &^ permission
}
type RoomMemberRole uint
const (
RoomMemberRoleUnknown RoomMemberRole = iota
RoomMemberRoleMember
RoomMemberRoleAdmin
RoomMemberRoleCreator
)
func (r RoomMemberRole) String() string {
switch r {
case RoomMemberRoleMember:
return "member"
case RoomMemberRoleAdmin:
return "admin"
default:
return "unknown"
}
}
func (r RoomMemberRole) IsCreator() bool {
return r == RoomMemberRoleCreator
}
func (r RoomMemberRole) IsAdmin() bool {
return r == RoomMemberRoleAdmin || r.IsCreator()
}
func (r RoomMemberRole) IsMember() bool {
return r == RoomMemberRoleMember || r.IsAdmin()
}
type RoomAdminPermission uint32
const (
PermissionApprovePendingMember RoomAdminPermission = 1 << iota
PermissionBanRoomMember
PermissionSetUserPermission
PermissionSetRoomSettings
PermissionSetRoomPassword
PermissionDeleteRoom
AllAdminPermissions RoomAdminPermission = math.MaxUint32
NoAdminPermission RoomAdminPermission = 0
DefaultAdminPermissions RoomAdminPermission = PermissionApprovePendingMember |
PermissionBanRoomMember |
PermissionSetUserPermission |
PermissionSetRoomSettings |
PermissionSetRoomPassword
)
func (p RoomAdminPermission) Has(permission RoomAdminPermission) bool {
return p&permission == permission
}
func (p RoomAdminPermission) Add(permission RoomAdminPermission) RoomAdminPermission {
return p | permission
}
func (p RoomAdminPermission) Remove(permission RoomAdminPermission) RoomAdminPermission {
return p &^ permission
}
type RoomMember struct {
CreatedAt time.Time
UpdatedAt time.Time
UserID string `gorm:"primarykey;type:char(32)"`
RoomID string `gorm:"primarykey;type:char(32)"`
Status RoomMemberStatus `gorm:"not null;default:2"`
Role RoomMemberRole `gorm:"not null;default:1"`
Permissions RoomMemberPermission
AdminPermissions RoomAdminPermission
}
var ErrNoPermission = errors.New("no permission")
func (r *RoomMember) HasPermission(permission RoomMemberPermission) bool {
switch r.Status {
case RoomMemberStatusActive:
return r.Permissions.Has(permission)
default:
return false
}
}
func (r *RoomMember) HasAdminPermission(permission RoomAdminPermission) bool {
switch r.Status {
case RoomMemberStatusActive:
if !r.Role.IsAdmin() {
return false
}
if r.Role.IsCreator() {
return true
}
return r.AdminPermissions.Has(permission)
default:
return false
}
}

@ -1,66 +0,0 @@
package model
import (
"errors"
"time"
)
type RoomUserStatus uint64
const (
RoomUserStatusBanned RoomUserStatus = iota + 1
RoomUserStatusPending
RoomUserStatusActive
)
func (r RoomUserStatus) String() string {
switch r {
case RoomUserStatusBanned:
return "banned"
case RoomUserStatusPending:
return "pending"
case RoomUserStatusActive:
return "active"
default:
return "unknown"
}
}
type RoomUserPermission uint64
const (
PermissionAll RoomUserPermission = 0xffffffff
PermissionEditRoom RoomUserPermission = 1 << iota
PermissionEditUser
PermissionCreateMovie
PermissionEditCurrent
PermissionSendChat
)
const (
DefaultPermissions = PermissionCreateMovie | PermissionEditCurrent | PermissionSendChat
)
func (p RoomUserPermission) Has(permission RoomUserPermission) bool {
return p&permission == permission
}
type RoomUserRelation struct {
CreatedAt time.Time
UpdatedAt time.Time
UserID string `gorm:"primarykey;type:char(32)"`
RoomID string `gorm:"primarykey;type:char(32)"`
Status RoomUserStatus `gorm:"not null;default:2"`
Permissions RoomUserPermission
}
var ErrNoPermission = errors.New("no permission")
func (r *RoomUserRelation) HasPermission(permission RoomUserPermission) bool {
switch r.Status {
case RoomUserStatusActive:
return r.Permissions.Has(permission)
default:
return false
}
}

@ -34,13 +34,13 @@ type Room struct {
ID string `gorm:"primaryKey;type:char(32)" json:"id"`
CreatedAt time.Time
UpdatedAt time.Time
Status RoomStatus `gorm:"not null;default:2"`
Name string `gorm:"not null;uniqueIndex;type:varchar(32)"`
Settings RoomSettings `gorm:"embedded;embeddedPrefix:settings_"`
CreatorID string `gorm:"index;type:char(32)"`
Status RoomStatus `gorm:"not null;default:2"`
Name string `gorm:"not null;uniqueIndex;type:varchar(32)"`
Settings *RoomSettings `gorm:"foreignKey:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE" json:"settings"`
CreatorID string `gorm:"index;type:char(32)"`
HashedPassword []byte
GroupUserRelations []*RoomUserRelation `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Movies []*Movie `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
GroupUserRelations []*RoomMember `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Movies []*Movie `gorm:"foreignKey:RoomID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
}
func (r *Room) BeforeCreate(tx *gorm.DB) error {
@ -50,16 +50,6 @@ func (r *Room) BeforeCreate(tx *gorm.DB) error {
return nil
}
type RoomSettings struct {
Hidden bool `json:"hidden"`
CanCreateMovie bool `gorm:"default:true" json:"canCreateMovie"`
CanEditCurrent bool `gorm:"default:true" json:"canEditCurrent"`
CanSendChat bool `gorm:"default:true" json:"canSendChat"`
DisableJoinNewUser bool `gorm:"default:false" json:"disableJoinNewUser"`
JoinNeedReview bool `gorm:"default:false" json:"joinNeedReview"`
UserDefaultPermissions RoomUserPermission `json:"userDefaultPermissions"`
}
func (r *Room) NeedPassword() bool {
return len(r.HashedPassword) != 0
}
@ -79,3 +69,37 @@ func (r *Room) IsPending() bool {
func (r *Room) IsActive() bool {
return r.Status == RoomStatusActive
}
type RoomSettings struct {
ID string `gorm:"primaryKey;type:char(32)" json:"-"`
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"-"`
Hidden bool `gorm:"default:false" json:"hidden"`
DisableJoinNewUser bool `gorm:"default:false" json:"disable_join_new_user"`
JoinNeedReview bool `gorm:"default:false" json:"join_need_review"`
UserDefaultPermissions RoomMemberPermission `json:"user_default_permissions"`
CanGetMovieList bool `gorm:"default:true" json:"can_get_movie_list"`
CanAddMovie bool `gorm:"default:true" json:"can_add_movie"`
CanDeleteMovie bool `gorm:"default:true" json:"can_delete_movie"`
CanEditMovie bool `gorm:"default:true" json:"can_edit_movie"`
CanSetCurrentMovie bool `gorm:"default:true" json:"can_set_current_movie"`
CanSetCurrentStatus bool `gorm:"default:true" json:"can_set_current_status"`
CanSendChatMessage bool `gorm:"default:true" json:"can_send_chat_message"`
}
func DefaultRoomSettings() *RoomSettings {
return &RoomSettings{
Hidden: false,
DisableJoinNewUser: false,
JoinNeedReview: false,
UserDefaultPermissions: DefaultPermissions,
CanGetMovieList: true,
CanAddMovie: true,
CanDeleteMovie: true,
CanEditMovie: true,
CanSetCurrentMovie: true,
CanSetCurrentStatus: true,
CanSendChatMessage: true,
}
}

@ -42,19 +42,19 @@ type User struct {
ID string `gorm:"primaryKey;type:char(32)" json:"id"`
CreatedAt time.Time
UpdatedAt time.Time
RegisteredByProvider bool `gorm:"not null;default:false"`
RegisteredByEmail bool `gorm:"not null;default:false"`
UserProviders []*UserProvider `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Username string `gorm:"not null;uniqueIndex;type:varchar(32)"`
HashedPassword []byte `gorm:"not null"`
Email string `gorm:"type:varchar(128);uniqueIndex:,where:email <> ''"`
Role Role `gorm:"not null;default:2"`
RoomUserRelations []*RoomUserRelation `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Rooms []*Room `gorm:"foreignKey:CreatorID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Movies []*Movie `gorm:"foreignKey:CreatorID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
BilibiliVendor *BilibiliVendor `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
AlistVendor []*AlistVendor `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
EmbyVendor []*EmbyVendor `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
RegisteredByProvider bool `gorm:"not null;default:false"`
RegisteredByEmail bool `gorm:"not null;default:false"`
UserProviders []*UserProvider `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Username string `gorm:"not null;uniqueIndex;type:varchar(32)"`
HashedPassword []byte `gorm:"not null"`
Email string `gorm:"type:varchar(128);uniqueIndex:,where:email <> ''"`
Role Role `gorm:"not null;default:2"`
RoomMembers []*RoomMember `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Rooms []*Room `gorm:"foreignKey:CreatorID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
Movies []*Movie `gorm:"foreignKey:CreatorID;constraint:OnUpdate:CASCADE,OnDelete:SET NULL"`
BilibiliVendor *BilibiliVendor `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
AlistVendor []*AlistVendor `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
EmbyVendor []*EmbyVendor `gorm:"foreignKey:UserID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE"`
}
func (u *User) CheckPassword(password string) bool {

@ -7,6 +7,8 @@ import (
"time"
"github.com/gorilla/websocket"
"github.com/synctv-org/synctv/internal/model"
pb "github.com/synctv-org/synctv/proto/message"
)
type Client struct {
@ -41,6 +43,23 @@ func (c *Client) Broadcast(msg Message, conf ...BroadcastConf) error {
return c.r.hub.Broadcast(msg, conf...)
}
func (c *Client) SendChatMessage(message string) error {
if c.u.HasRoomPermission(c.r, model.PermissionSendChatMessage) {
return model.ErrNoPermission
}
return c.Broadcast(&pb.ElementMessage{
Type: pb.ElementMessageType_CHAT_MESSAGE,
Time: time.Now().UnixMilli(),
ChatResp: &pb.ChatResp{
Message: message,
Sender: &pb.Sender{
Userid: c.u.ID,
Username: c.u.Username,
},
},
})
}
func (c *Client) Send(msg Message) error {
c.wg.Add(1)
defer c.wg.Done()
@ -75,3 +94,11 @@ func (c *Client) NextWriter(messageType int) (io.WriteCloser, error) {
func (c *Client) NextReader() (int, io.Reader, error) {
return c.conn.NextReader()
}
func (c *Client) SetSeekRate(seek float64, rate float64, timeDiff float64) (*Status, error) {
return c.u.SetRoomCurrentSeekRate(c.r, seek, rate, timeDiff)
}
func (c *Client) SetStatus(playing bool, seek float64, rate float64, timeDiff float64) (*Status, error) {
return c.u.SetRoomCurrentStatus(c.r, playing, seek, rate, timeDiff)
}

@ -63,18 +63,20 @@ func (c *current) Status() Status {
return c.current.Status
}
func (c *current) SetStatus(playing bool, seek, rate, timeDiff float64) Status {
func (c *current) SetStatus(playing bool, seek, rate, timeDiff float64) *Status {
c.lock.Lock()
defer c.lock.Unlock()
return c.current.SetStatus(playing, seek, rate, timeDiff)
s := c.current.SetStatus(playing, seek, rate, timeDiff)
return &s
}
func (c *current) SetSeekRate(seek, rate, timeDiff float64) Status {
func (c *current) SetSeekRate(seek, rate, timeDiff float64) *Status {
c.lock.Lock()
defer c.lock.Unlock()
return c.current.SetSeekRate(seek, rate, timeDiff)
s := c.current.SetSeekRate(seek, rate, timeDiff)
return &s
}
func (c *Current) UpdateStatus() Status {

@ -23,7 +23,7 @@ import (
)
type Movie struct {
model.Movie
*model.Movie
channel atomic.Pointer[rtmps.Channel]
alistCache atomic.Pointer[cache.AlistMovieCache]
bilibiliCache atomic.Pointer[cache.BilibiliMovieCache]
@ -70,7 +70,7 @@ func (m *Movie) ClearCache() {
func (m *Movie) AlistCache() *cache.AlistMovieCache {
c := m.alistCache.Load()
if c == nil {
c = cache.NewAlistMovieCache(&m.Movie)
c = cache.NewAlistMovieCache(m.Movie)
if !m.alistCache.CompareAndSwap(nil, c) {
return m.AlistCache()
}
@ -81,7 +81,7 @@ func (m *Movie) AlistCache() *cache.AlistMovieCache {
func (m *Movie) BilibiliCache() *cache.BilibiliMovieCache {
c := m.bilibiliCache.Load()
if c == nil {
c = cache.NewBilibiliMovieCache(&m.Movie)
c = cache.NewBilibiliMovieCache(m.Movie)
if !m.bilibiliCache.CompareAndSwap(nil, c) {
return m.BilibiliCache()
}
@ -92,7 +92,7 @@ func (m *Movie) BilibiliCache() *cache.BilibiliMovieCache {
func (m *Movie) EmbyCache() *cache.EmbyMovieCache {
c := m.embyCache.Load()
if c == nil {
c = cache.NewEmbyMovieCache(&m.Movie)
c = cache.NewEmbyMovieCache(m.Movie)
if !m.embyCache.CompareAndSwap(nil, c) {
return m.EmbyCache()
}

@ -23,7 +23,7 @@ func (m *movies) init() {
m.once.Do(func() {
for _, m2 := range db.GetAllMoviesByRoomID(m.roomID) {
m.list.PushBack(&Movie{
Movie: *m2,
Movie: m2,
})
}
})
@ -42,7 +42,7 @@ func (m *movies) AddMovie(mo *model.Movie) error {
defer m.lock.Unlock()
mo.Position = uint(time.Now().UnixMilli())
movie := &Movie{
Movie: *mo,
Movie: mo,
}
err := movie.Validate()
@ -55,7 +55,6 @@ func (m *movies) AddMovie(mo *model.Movie) error {
return err
}
movie.Movie.ID = mo.ID
m.list.PushBack(movie)
return nil
}
@ -68,7 +67,7 @@ func (m *movies) AddMovies(mos []*model.Movie) error {
for _, mo := range mos {
mo.Position = uint(time.Now().UnixMilli())
movie := &Movie{
Movie: *mo,
Movie: mo,
}
err := movie.Validate()
@ -84,8 +83,7 @@ func (m *movies) AddMovies(mos []*model.Movie) error {
return err
}
for i, mo := range inited {
mo.Movie.ID = mos[i].ID
for _, mo := range inited {
m.list.PushBack(mo)
}
@ -113,7 +111,7 @@ func (m *movies) Update(movieId string, movie *model.BaseMovie) error {
if err != nil {
return err
}
return db.SaveMovie(&e.Value.Movie)
return db.SaveMovie(e.Value.Movie)
}
}
return nil
@ -238,21 +236,35 @@ func (m *movies) SwapMoviePositions(id1, id2 string) error {
return nil
}
func (m *movies) GetMoviesWithPage(page, pageSize int) []*Movie {
func (m *movies) GetMoviesWithPage(page, pageSize int, creator string) ([]*Movie, int) {
m.init()
m.lock.RLock()
defer m.lock.RUnlock()
start, end := utils.GetPageItemsRange(m.list.Len(), page, pageSize)
var total int
if creator != "" {
for e := m.list.Front(); e != nil; e = e.Next() {
if e.Value.Movie.CreatorID == creator {
total++
}
}
} else {
total = m.list.Len()
}
start, end := utils.GetPageItemsRange(total, page, pageSize)
ms := make([]*Movie, 0, end-start)
i := 0
for e := m.list.Front(); e != nil; e = e.Next() {
if creator != "" && e.Value.Movie.CreatorID != creator {
continue
}
if i >= start && i < end {
ms = append(ms, e.Value)
} else if i >= end {
return ms
return ms, total
}
i++
}
return ms
return ms, total
}

@ -6,6 +6,7 @@ import (
"sync/atomic"
"github.com/gorilla/websocket"
log "github.com/sirupsen/logrus"
"github.com/synctv-org/synctv/internal/db"
"github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/utils"
@ -88,12 +89,55 @@ func (r *Room) AddMovies(movies []*model.Movie) error {
return r.movies.AddMovies(movies)
}
func (r *Room) HasPermission(userID string, permission model.RoomUserPermission) bool {
func (r *Room) UserRole(userID string) (model.RoomMemberRole, error) {
if r.CreatorID == userID {
return model.RoomMemberRoleCreator, nil
}
rur, err := r.LoadOrCreateRoomMember(userID)
if err != nil {
return model.RoomMemberRoleUnknown, err
}
return rur.Role, nil
}
// do not use this value for permission determination
func (r *Room) IsAdmin(userID string) bool {
role, err := r.UserRole(userID)
if err != nil {
log.Errorf("get user role failed: %s", err.Error())
return false
}
return role == model.RoomMemberRoleCreator
}
func (r *Room) HasAdminPermission(userID string, permission model.RoomAdminPermission) bool {
if r.CreatorID == userID {
return true
}
rur, err := r.LoadOrCreateRoomMember(userID)
if err != nil {
return false
}
return rur.HasAdminPermission(permission)
}
func (r *Room) UserStatus(userID string) (model.RoomMemberStatus, error) {
if r.CreatorID == userID {
return model.RoomMemberStatusActive, nil
}
rur, err := r.LoadOrCreateRoomMember(userID)
if err != nil {
return model.RoomMemberStatusUnknown, err
}
return rur.Status, nil
}
func (r *Room) HasPermission(userID string, permission model.RoomMemberPermission) bool {
if r.CreatorID == userID {
return true
}
rur, err := r.LoadOrCreateRoomUserRelation(userID)
rur, err := r.LoadOrCreateRoomMember(userID)
if err != nil {
return false
}
@ -101,24 +145,35 @@ func (r *Room) HasPermission(userID string, permission model.RoomUserPermission)
return rur.HasPermission(permission)
}
func (r *Room) LoadOrCreateRoomUserRelation(userID string) (*model.RoomUserRelation, error) {
var conf []db.CreateRoomUserRelationConfig
if r.Settings.JoinNeedReview {
conf = []db.CreateRoomUserRelationConfig{db.WithRoomUserRelationStatus(model.RoomUserStatusPending)}
func (r *Room) LoadOrCreateRoomMember(userID string) (*model.RoomMember, error) {
var conf []db.CreateRoomMemberRelationConfig
if r.CreatorID == userID {
conf = append(
conf,
db.WithRoomMemberStatus(model.RoomMemberStatusActive),
db.WithRoomMemberRelationPermissions(model.AllPermissions),
db.WithRoomMemberRole(model.RoomMemberRoleCreator),
db.WithRoomMemberAdminPermissions(model.AllAdminPermissions),
)
} else {
conf = []db.CreateRoomUserRelationConfig{db.WithRoomUserRelationStatus(model.RoomUserStatusActive)}
}
if r.Settings.UserDefaultPermissions != 0 {
conf = append(conf, db.WithRoomUserRelationPermissions(r.Settings.UserDefaultPermissions))
conf = append(
conf,
db.WithRoomMemberRelationPermissions(r.Settings.UserDefaultPermissions),
)
if r.Settings.JoinNeedReview {
conf = append(conf, db.WithRoomMemberStatus(model.RoomMemberStatusPending))
} else {
conf = append(conf, db.WithRoomMemberStatus(model.RoomMemberStatusActive))
}
}
return db.FirstOrCreateRoomUserRelation(r.ID, userID, conf...)
return db.FirstOrCreateRoomMemberRelation(r.ID, userID, conf...)
}
func (r *Room) GetRoomUserRelation(userID string) (model.RoomUserPermission, error) {
func (r *Room) GetRoomMemberPermission(userID string) (model.RoomMemberPermission, error) {
if r.CreatorID == userID {
return model.PermissionAll, nil
return model.AllPermissions, nil
}
ur, err := db.GetRoomUserRelation(r.ID, userID)
ur, err := db.GetRoomMemberRelation(r.ID, userID)
if err != nil {
return 0, err
}
@ -146,20 +201,16 @@ func (r *Room) SetPassword(password string) error {
return db.SetRoomHashedPassword(r.ID, hashedPassword)
}
func (r *Room) SetUserStatus(userID string, status model.RoomUserStatus) error {
return db.SetRoomUserStatus(r.ID, userID, status)
}
func (r *Room) SetUserPermission(userID string, permission model.RoomUserPermission) error {
return db.SetUserPermission(r.ID, userID, permission)
func (r *Room) SetUserPermission(userID string, permission model.RoomMemberPermission) error {
return db.SetMemberPermissions(r.ID, userID, permission)
}
func (r *Room) AddUserPermission(userID string, permission model.RoomUserPermission) error {
return db.AddUserPermission(r.ID, userID, permission)
func (r *Room) AddUserPermission(userID string, permission model.RoomMemberPermission) error {
return db.AddMemberPermissions(r.ID, userID, permission)
}
func (r *Room) RemoveUserPermission(userID string, permission model.RoomUserPermission) error {
return db.RemoveUserPermission(r.ID, userID, permission)
func (r *Room) RemoveUserPermission(userID string, permission model.RoomMemberPermission) error {
return db.RemoveMemberPermissions(r.ID, userID, permission)
}
func (r *Room) GetMoviesCount() int {
@ -234,8 +285,8 @@ func (r *Room) SwapMoviePositions(id1, id2 string) error {
return r.movies.SwapMoviePositions(id1, id2)
}
func (r *Room) GetMoviesWithPage(page, pageSize int) []*Movie {
return r.movies.GetMoviesWithPage(page, pageSize)
func (r *Room) GetMoviesWithPage(page, pageSize int, creator string) ([]*Movie, int) {
return r.movies.GetMoviesWithPage(page, pageSize, creator)
}
func (r *Room) NewClient(user *User, conn *websocket.Conn) (*Client, error) {
@ -258,28 +309,80 @@ func (r *Room) UnregisterClient(cli *Client) error {
return r.hub.UnRegClient(cli)
}
func (r *Room) SetStatus(playing bool, seek float64, rate float64, timeDiff float64) Status {
func (r *Room) SetCurrentStatus(playing bool, seek float64, rate float64, timeDiff float64) *Status {
return r.current.SetStatus(playing, seek, rate, timeDiff)
}
func (r *Room) SetSeekRate(seek float64, rate float64, timeDiff float64) Status {
func (r *Room) SetCurrentSeekRate(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)
func (r *Room) SetSettings(settings *model.RoomSettings) error {
err := db.SaveRoomSettings(r.ID, settings)
if err != nil {
return err
}
r.Status = status
r.Settings = settings
return nil
}
func (r *Room) SetSettings(settings model.RoomSettings) error {
err := db.SaveRoomSettings(r.ID, settings)
func (r *Room) UpdateSettings(settings map[string]any) error {
rs, err := db.UpdateRoomSettings(r.ID, settings)
if err != nil {
return err
}
r.Settings = settings
r.Settings = rs
return nil
}
func (r *Room) ResetMemberPermissions(userID string) error {
return r.SetMemberPermissions(userID, r.Settings.UserDefaultPermissions)
}
func (r *Room) SetMemberPermissions(userID string, permissions model.RoomMemberPermission) error {
return db.SetMemberPermissions(r.ID, userID, permissions)
}
func (r *Room) AddMemberPermissions(userID string, permissions model.RoomMemberPermission) error {
return db.AddMemberPermissions(r.ID, userID, permissions)
}
func (r *Room) RemoveMemberPermissions(userID string, permissions model.RoomMemberPermission) error {
return db.RemoveMemberPermissions(r.ID, userID, permissions)
}
func (r *Room) ApprovePendingMember(userID string) error {
return db.RoomApprovePendingMember(r.ID, userID)
}
func (r *Room) BanMember(userID string) error {
return db.RoomBanMember(r.ID, userID)
}
func (r *Room) UnbanMember(userID string) error {
return db.RoomUnbanMember(r.ID, userID)
}
func (r *Room) ResetAdminPermissions(userID string) error {
return r.SetAdminPermissions(userID, model.DefaultAdminPermissions)
}
func (r *Room) SetAdminPermissions(userID string, permissions model.RoomAdminPermission) error {
return db.RoomSetAdminPermissions(r.ID, userID, permissions)
}
func (r *Room) AddAdminPermissions(userID string, permissions model.RoomAdminPermission) error {
return db.RoomSetAdminPermissions(r.ID, userID, permissions)
}
func (r *Room) RemoveAdminPermissions(userID string, permissions model.RoomAdminPermission) error {
return db.RoomSetAdminPermissions(r.ID, userID, 0)
}
func (r *Room) SetAdmin(userID string, permissions model.RoomAdminPermission) error {
return db.RoomSetAdmin(r.ID, userID, permissions)
}
func (r *Room) SetMember(userID string, permissions model.RoomMemberPermission) error {
return db.RoomSetMember(r.ID, userID, permissions)
}

@ -124,7 +124,7 @@ func (u *User) NewMovie(movie *model.BaseMovie) (*model.Movie, error) {
}
func (u *User) AddMovieToRoom(room *Room, movie *model.BaseMovie) error {
if !u.HasRoomPermission(room, model.PermissionCreateMovie) {
if !u.HasRoomPermission(room, model.PermissionAddMovie) {
return model.ErrNoPermission
}
m, err := u.NewMovie(movie)
@ -157,7 +157,7 @@ func (u *User) NewMovies(movies []*model.BaseMovie) ([]*model.Movie, error) {
}
func (u *User) AddMoviesToRoom(room *Room, movies []*model.BaseMovie) error {
if !u.HasRoomPermission(room, model.PermissionCreateMovie) {
if !u.HasRoomPermission(room, model.PermissionAddMovie) {
return model.ErrNoPermission
}
m, err := u.NewMovies(movies)
@ -193,22 +193,40 @@ func (u *User) IsPending() bool {
return u.Role == model.RolePending
}
func (u *User) HasRoomPermission(room *Room, permission model.RoomUserPermission) bool {
func (u *User) HasRoomPermission(room *Room, permission model.RoomMemberPermission) bool {
if u.IsAdmin() {
return true
}
return room.HasPermission(u.ID, permission)
}
func (u *User) HasRoomAdminPermission(room *Room, permission model.RoomAdminPermission) bool {
if u.IsAdmin() {
return true
}
return room.HasAdminPermission(u.ID, permission)
}
func (u *User) IsRoomAdmin(room *Room) bool {
if u.IsAdmin() {
return true
}
return room.IsAdmin(u.ID)
}
func (u *User) IsRoomCreator(room *Room) bool {
return room.CreatorID == u.ID
}
func (u *User) DeleteRoom(room *RoomEntry) error {
if !u.HasRoomPermission(room.Value(), model.PermissionEditRoom) {
if !u.HasRoomAdminPermission(room.Value(), model.PermissionDeleteRoom) {
return model.ErrNoPermission
}
return CompareAndDeleteRoom(room)
}
func (u *User) SetRoomPassword(room *Room, password string) error {
if !u.HasRoomPermission(room, model.PermissionEditRoom) {
if !u.HasRoomAdminPermission(room, model.PermissionSetRoomPassword) {
return model.ErrNoPermission
}
if !u.IsAdmin() && password == "" && settings.RoomMustNeedPwd.Get() {
@ -238,7 +256,7 @@ func (u *User) UpdateMovie(room *Room, movieID string, movie *model.BaseMovie) e
if err != nil {
return err
}
if m.Movie.CreatorID != u.ID && !u.HasRoomPermission(room, model.PermissionEditUser) {
if m.Movie.CreatorID != u.ID && !u.HasRoomPermission(room, model.PermissionEditMovie) {
return model.ErrNoPermission
}
err = room.UpdateMovie(movieID, movie)
@ -254,19 +272,26 @@ func (u *User) UpdateMovie(room *Room, movieID string, movie *model.BaseMovie) e
})
}
func (u *User) SetRoomSetting(room *Room, setting model.RoomSettings) error {
if !u.HasRoomPermission(room, model.PermissionEditRoom) {
func (u *User) SetRoomSetting(room *Room, setting *model.RoomSettings) error {
if !u.HasRoomAdminPermission(room, model.PermissionSetRoomSettings) {
return model.ErrNoPermission
}
return room.SetSettings(setting)
}
func (u *User) UpdateRoomSettings(room *Room, settings map[string]interface{}) error {
if !u.HasRoomAdminPermission(room, model.PermissionSetRoomSettings) {
return model.ErrNoPermission
}
return room.UpdateSettings(settings)
}
func (u *User) DeleteMovieByID(room *Room, movieID string) error {
m, err := room.GetMovieByID(movieID)
if err != nil {
return err
}
if m.Movie.CreatorID != u.ID && !u.HasRoomPermission(room, model.PermissionEditUser) {
if m.Movie.CreatorID != u.ID && !u.HasRoomPermission(room, model.PermissionDeleteMovie) {
return model.ErrNoPermission
}
return room.DeleteMovieByID(movieID)
@ -278,7 +303,7 @@ func (u *User) DeleteMoviesByID(room *Room, movieIDs []string) error {
if err != nil {
return err
}
if m.Movie.CreatorID != u.ID && !u.HasRoomPermission(room, model.PermissionEditUser) {
if m.Movie.CreatorID != u.ID && !u.HasRoomPermission(room, model.PermissionDeleteMovie) {
return model.ErrNoPermission
}
}
@ -295,7 +320,7 @@ func (u *User) DeleteMoviesByID(room *Room, movieIDs []string) error {
}
func (u *User) ClearMovies(room *Room) error {
if !u.HasRoomPermission(room, model.PermissionEditRoom) {
if !u.HasRoomPermission(room, model.PermissionDeleteMovie) {
return model.ErrNoPermission
}
err := room.ClearMovies()
@ -312,7 +337,7 @@ func (u *User) ClearMovies(room *Room) error {
}
func (u *User) SwapMoviePositions(room *Room, id1, id2 string) error {
if !u.HasRoomPermission(room, model.PermissionEditRoom) {
if !u.HasRoomPermission(room, model.PermissionEditMovie) {
return model.ErrNoPermission
}
err := room.SwapMoviePositions(id1, id2)
@ -329,7 +354,7 @@ func (u *User) SwapMoviePositions(room *Room, id1, id2 string) error {
}
func (u *User) SetCurrentMovie(room *Room, movieID string, play bool) error {
if !u.HasRoomPermission(room, model.PermissionEditCurrent) {
if !u.HasRoomPermission(room, model.PermissionSetCurrentMovie) {
return model.ErrNoPermission
}
err := room.SetCurrentMovie(movieID, play)
@ -403,3 +428,115 @@ func (u *User) VerifyRetrievePasswordCaptchaEmail(e, captcha string) (bool, erro
}
return email.VerifyRetrievePasswordCaptchaEmail(u.ID, e, captcha)
}
func (u *User) GetRoomMoviesWithPage(room *Room, page, pageSize int) ([]*Movie, int) {
if u.HasRoomPermission(room, model.PermissionGetMovieList) {
return room.GetMoviesWithPage(page, pageSize, "")
}
return room.GetMoviesWithPage(page, pageSize, u.ID)
}
func (u *User) SetRoomCurrentSeekRate(room *Room, seek, rate, timeDiff float64) (*Status, error) {
if !u.HasRoomPermission(room, model.PermissionSetCurrentStatus) {
return nil, model.ErrNoPermission
}
return room.SetCurrentSeekRate(seek, rate, timeDiff), nil
}
func (u *User) SetRoomCurrentStatus(room *Room, playing bool, seek, rate, timeDiff float64) (*Status, error) {
if !u.HasRoomPermission(room, model.PermissionSetCurrentStatus) {
return nil, model.ErrNoPermission
}
return room.SetCurrentStatus(playing, seek, rate, timeDiff), nil
}
func (u *User) BanRoomMember(room *Room, userID string) error {
if !u.HasRoomAdminPermission(room, model.PermissionBanRoomMember) {
return model.ErrNoPermission
}
return room.BanMember(userID)
}
func (u *User) UnbanRoomMember(room *Room, userID string) error {
if !u.HasRoomAdminPermission(room, model.PermissionBanRoomMember) {
return model.ErrNoPermission
}
return room.UnbanMember(userID)
}
func (u *User) SetMemberPermissions(room *Room, userID string, permissions model.RoomMemberPermission) error {
if !u.HasRoomAdminPermission(room, model.PermissionSetUserPermission) {
return model.ErrNoPermission
}
return room.SetMemberPermissions(userID, permissions)
}
func (u *User) AddMemberPermissions(room *Room, userID string, permissions model.RoomMemberPermission) error {
if !u.HasRoomAdminPermission(room, model.PermissionSetUserPermission) {
return model.ErrNoPermission
}
return room.AddMemberPermissions(userID, permissions)
}
func (u *User) RemoveMemberPermissions(room *Room, userID string, permissions model.RoomMemberPermission) error {
if !u.HasRoomAdminPermission(room, model.PermissionSetUserPermission) {
return model.ErrNoPermission
}
return room.RemoveMemberPermissions(userID, permissions)
}
func (u *User) ResetMemberPermissions(room *Room, userID string) error {
if !u.HasRoomAdminPermission(room, model.PermissionSetUserPermission) {
return model.ErrNoPermission
}
return room.ResetMemberPermissions(userID)
}
func (u *User) ApproveRoomPendingMember(room *Room, userID string) error {
if !u.HasRoomAdminPermission(room, model.PermissionApprovePendingMember) {
return model.ErrNoPermission
}
return room.ApprovePendingMember(userID)
}
func (u *User) SetRoomAdmin(room *Room, userID string, permissions model.RoomAdminPermission) error {
if !u.IsRoomCreator(room) {
return model.ErrNoPermission
}
return room.SetAdmin(userID, permissions)
}
func (u *User) SetRoomMember(room *Room, userID string, permissions model.RoomMemberPermission) error {
if !u.IsRoomCreator(room) {
return model.ErrNoPermission
}
return room.SetMember(userID, permissions)
}
func (u *User) SetRoomAdminPermissions(room *Room, userID string, permissions model.RoomAdminPermission) error {
if !u.IsRoomCreator(room) {
return model.ErrNoPermission
}
return room.SetAdminPermissions(userID, permissions)
}
func (u *User) AddRoomAdminPermissions(room *Room, userID string, permissions model.RoomAdminPermission) error {
if !u.IsRoomCreator(room) {
return model.ErrNoPermission
}
return room.AddAdminPermissions(userID, permissions)
}
func (u *User) RemoveRoomAdminPermissions(room *Room, userID string, permissions model.RoomAdminPermission) error {
if !u.IsRoomCreator(room) {
return model.ErrNoPermission
}
return room.RemoveAdminPermissions(userID, permissions)
}
func (u *User) ResetRoomAdminPermissions(room *Room, userID string) error {
if !u.IsRoomCreator(room) {
return model.ErrNoPermission
}
return room.ResetAdminPermissions(userID)
}

@ -154,17 +154,43 @@ func Users(ctx *gin.Context) {
// search mode, all, name, id
switch ctx.DefaultQuery("search", "all") {
case "all":
scopes = append(scopes, db.WhereUsernameLikeOrIDIn(keyword, db.GerUsersIDByIDLike(keyword)))
ids, err := db.GerUsersIDByIDLike(keyword)
if err != nil {
log.WithError(err).Error("get users id by id like error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
scopes = append(scopes, db.WhereUsernameLikeOrIDIn(keyword, ids))
case "name":
scopes = append(scopes, db.WhereUsernameLike(keyword))
case "id":
scopes = append(scopes, db.WhereIDIn(db.GerUsersIDByIDLike(keyword)))
ids, err := db.GerUsersIDByIDLike(keyword)
if err != nil {
log.WithError(err).Error("get users id by id like error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
scopes = append(scopes, db.WhereIDIn(ids))
}
}
total, err := db.GetAllUserCount(scopes...)
if err != nil {
log.WithError(err).Error("get all user count error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
list, err := db.GetAllUsers(append(scopes, db.Paginate(page, pageSize))...)
if err != nil {
log.WithError(err).Error("get all users error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": db.GetAllUserCount(scopes...),
"list": genUserListResp(db.GetAllUsers(append(scopes, db.Paginate(page, pageSize))...)),
"total": total,
"list": genUserListResp(list),
}))
}
@ -181,31 +207,32 @@ func genUserListResp(us []*dbModel.User) []*model.UserInfoResp {
return resp
}
func GetRoomUsers(ctx *gin.Context) {
func AdminGetRoomMembers(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
id := ctx.Query("id")
if len(id) != 32 {
log.Error("room id error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("room id error"))
return
}
page, pageSize, err := utils.GetPageAndMax(ctx)
if err != nil {
log.WithError(err).Error("get page and max error")
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
var desc = ctx.DefaultQuery("order", "desc") == "desc"
scopes := []func(db *gorm.DB) *gorm.DB{
db.PreloadRoomUserRelations(db.WhereRoomID(id)),
scopes := []func(db *gorm.DB) *gorm.DB{}
switch ctx.DefaultQuery("status", "active") {
case "pending":
scopes = append(scopes, db.WhereRoomMemberStatus(dbModel.RoomMemberStatusPending))
case "banned":
scopes = append(scopes, db.WhereRoomMemberStatus(dbModel.RoomMemberStatusBanned))
case "active":
scopes = append(scopes, db.WhereRoomMemberStatus(dbModel.RoomMemberStatusActive))
}
switch ctx.DefaultQuery("sort", "name") {
case "createdAt":
case "join":
if desc {
scopes = append(scopes, db.OrderByCreatedAtDesc)
} else {
@ -218,7 +245,7 @@ func GetRoomUsers(ctx *gin.Context) {
scopes = append(scopes, db.OrderByAsc("username"))
}
default:
log.Error("not support sort")
log.Errorf("get room users failed: not support sort")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support sort"))
return
}
@ -227,31 +254,60 @@ func GetRoomUsers(ctx *gin.Context) {
// search mode, all, name, id
switch ctx.DefaultQuery("search", "all") {
case "all":
scopes = append(scopes, db.WhereUsernameLikeOrIDIn(keyword, db.GerUsersIDByIDLike(keyword)))
ids, err := db.GerUsersIDByIDLike(keyword)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
scopes = append(scopes, db.WhereUsernameLikeOrIDIn(keyword, ids))
case "name":
scopes = append(scopes, db.WhereUsernameLike(keyword))
case "id":
scopes = append(scopes, db.WhereIDIn(db.GerUsersIDByIDLike(keyword)))
ids, err := db.GerUsersIDByIDLike(keyword)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
scopes = append(scopes, db.WhereIDIn(ids))
}
}
scopes = append(scopes, func(db *gorm.DB) *gorm.DB {
return db.InnerJoins("JOIN room_members ON users.id = room_members.user_id AND room_members.room_id = ?", room.ID)
}, db.PreloadRoomMembers())
total, err := db.GetAllUserCount(scopes...)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
list, err := db.GetAllUsers(append(scopes, db.Paginate(page, pageSize))...)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": db.GetAllUserCount(scopes...),
"list": genRoomUserListResp(db.GetAllUsers(append(scopes, db.Paginate(page, pageSize))...)),
"total": total,
"list": genRoomMemberListResp(list),
}))
}
func genRoomUserListResp(us []*dbModel.User) []*model.RoomUsersResp {
resp := make([]*model.RoomUsersResp, len(us))
func genRoomMemberListResp(us []*dbModel.User) []*model.RoomMembersResp {
resp := make([]*model.RoomMembersResp, len(us))
for i, v := range us {
resp[i] = &model.RoomUsersResp{
UserID: v.ID,
Username: v.Username,
Role: v.Role,
JoinAt: v.RoomUserRelations[0].CreatedAt.UnixMilli(),
RoomID: v.RoomUserRelations[0].RoomID,
Status: v.RoomUserRelations[0].Status,
Permissions: v.RoomUserRelations[0].Permissions,
resp[i] = &model.RoomMembersResp{
UserID: v.ID,
Username: v.Username,
JoinAt: v.RoomMembers[0].CreatedAt.UnixMilli(),
Role: v.RoomMembers[0].Role,
RoomID: v.RoomMembers[0].RoomID,
Permissions: v.RoomMembers[0].Permissions,
AdminPermissions: v.RoomMembers[0].AdminPermissions,
}
}
return resp
@ -409,11 +465,23 @@ func Rooms(ctx *gin.Context) {
// search mode, all, name, creator
switch ctx.DefaultQuery("search", "all") {
case "all":
scopes = append(scopes, db.WhereRoomNameLikeOrCreatorInOrIDLike(keyword, db.GerUsersIDByUsernameLike(keyword), keyword))
ids, err := db.GerUsersIDByUsernameLike(keyword)
if err != nil {
log.WithError(err).Error("get users id by username like error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
scopes = append(scopes, db.WhereRoomNameLikeOrCreatorInOrIDLike(keyword, ids, keyword))
case "name":
scopes = append(scopes, db.WhereRoomNameLike(keyword))
case "creator":
scopes = append(scopes, db.WhereCreatorIDIn(db.GerUsersIDByUsernameLike(keyword)))
ids, err := db.GerUsersIDByUsernameLike(keyword)
if err != nil {
log.WithError(err).Error("get users id by username like error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
scopes = append(scopes, db.WhereCreatorIDIn(ids))
case "creatorId":
scopes = append(scopes, db.WhereCreatorID(keyword))
case "id":
@ -421,9 +489,23 @@ func Rooms(ctx *gin.Context) {
}
}
total, err := db.GetAllRoomsCount(scopes...)
if err != nil {
log.WithError(err).Error("get all rooms count error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
list, err := genRoomListResp(append(scopes, db.Paginate(page, pageSize))...)
if err != nil {
log.WithError(err).Error("gen room list resp error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": db.GetAllRoomsCount(scopes...),
"list": genRoomListResp(append(scopes, db.Paginate(page, pageSize))...),
"total": total,
"list": list,
}))
}
@ -489,9 +571,24 @@ func GetUserRooms(ctx *gin.Context) {
}
}
total, err := db.GetAllRoomsCount(scopes...)
if err != nil {
log.WithError(err).Error("get all rooms count error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
list, err := genRoomListResp(append(scopes, db.Paginate(page, pageSize))...)
if err != nil {
log.WithError(err).Error("gen room list resp error")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": db.GetAllRoomsCount(scopes...),
"list": genRoomListResp(append(scopes, db.Paginate(page, pageSize))...),
"total": total,
"list": list,
}))
}

@ -147,7 +147,7 @@ func initAdmin(admin *gin.RouterGroup, root *gin.RouterGroup) {
room.POST("/unban", UnBanRoom)
room.GET("/users", GetRoomUsers)
room.GET("/members", AdminGetRoomMembers)
}
}
@ -171,19 +171,42 @@ func initRoom(room *gin.RouterGroup, needAuthUser *gin.RouterGroup, needAuthRoom
needAuthUser.POST("/login", LoginRoom)
needAuthRoom.POST("/delete", DeleteRoom)
needAuthRoom.GET("/me", RoomMe)
needAuthRoom.POST("/pwd", SetRoomPassword)
needAuthRoom.GET("/members", RoomMembers)
needAuthRoom.GET("/settings", RoomSetting)
{
needAuthRoomAdmin := needAuthRoom.Group("/admin", middlewares.AuthRoomAdminMiddleware)
needAuthRoomCreator := needAuthRoom.Group("/admin", middlewares.AuthRoomCreatorMiddleware)
needAuthRoomAdmin.GET("/settings", RoomSetting)
needAuthRoomAdmin.POST("/settings", SetRoomSetting)
needAuthRoomAdmin.POST("/delete", DeleteRoom)
needAuthRoomAdmin.POST("/pwd", SetRoomPassword)
needAuthRoomAdmin.GET("/members", RoomAdminMembers)
needAuthRoom.POST("/settings", SetRoomSetting)
needAuthRoomAdmin.POST("/members/approve", RoomAdminApproveMember)
needAuthRoom.GET("/users", RoomUsers)
needAuthRoomAdmin.POST("/members/ban", RoomAdminBanMember)
needAuthRoomAdmin.POST("/members/unban", RoomAdminUnbanMember)
needAuthRoomCreator.POST("/members/permissions", RoomSetMemberPermissions)
needAuthRoomCreator.POST("/members", RoomSetMember)
needAuthRoomCreator.POST("/members/admin", RoomSetAdmin)
needAuthRoomCreator.POST("/members/admin/permissions", RoomSetAdminPermissions)
}
}
func initMovie(movie *gin.RouterGroup, needAuthMovie *gin.RouterGroup) {
needAuthMovie.GET("/list", MovieList)
// needAuthMovie.GET("/list", MovieList)
needAuthMovie.GET("/current", CurrentMovie)

@ -0,0 +1,339 @@
package handlers
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
"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"
"github.com/synctv-org/synctv/utils"
"gorm.io/gorm"
)
func RoomMembers(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
page, pageSize, err := utils.GetPageAndMax(ctx)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
var desc = ctx.DefaultQuery("order", "desc") == "desc"
scopes := []func(db *gorm.DB) *gorm.DB{}
switch ctx.DefaultQuery("sort", "name") {
case "join":
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"))
}
default:
log.Errorf("get room users failed: not support sort")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support sort"))
return
}
if keyword := ctx.Query("keyword"); keyword != "" {
// search mode, all, name, id
switch ctx.DefaultQuery("search", "all") {
case "all":
ids, err := db.GerUsersIDByIDLike(keyword)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
scopes = append(scopes, db.WhereUsernameLikeOrIDIn(keyword, ids))
case "name":
scopes = append(scopes, db.WhereUsernameLike(keyword))
case "id":
ids, err := db.GerUsersIDByIDLike(keyword)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
scopes = append(scopes, db.WhereIDIn(ids))
}
}
scopes = append(scopes, func(db *gorm.DB) *gorm.DB {
return db.InnerJoins("JOIN room_members ON users.id = room_members.user_id AND room_members.room_id = ?", room.ID)
}, db.PreloadRoomMembers())
total, err := db.GetAllUserCount(scopes...)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
list, err := db.GetAllUsers(append(scopes, db.Paginate(page, pageSize))...)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": total,
"list": genRoomMemberListResp(list),
}))
}
func RoomAdminMembers(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
page, pageSize, err := utils.GetPageAndMax(ctx)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
var desc = ctx.DefaultQuery("order", "desc") == "desc"
scopes := []func(db *gorm.DB) *gorm.DB{}
switch ctx.DefaultQuery("status", "active") {
case "pending":
scopes = append(scopes, db.WhereRoomMemberStatus(dbModel.RoomMemberStatusPending))
case "banned":
scopes = append(scopes, db.WhereRoomMemberStatus(dbModel.RoomMemberStatusBanned))
case "active":
scopes = append(scopes, db.WhereRoomMemberStatus(dbModel.RoomMemberStatusActive))
}
switch ctx.DefaultQuery("sort", "name") {
case "join":
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"))
}
default:
log.Errorf("get room users failed: not support sort")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support sort"))
return
}
if keyword := ctx.Query("keyword"); keyword != "" {
// search mode, all, name, id
switch ctx.DefaultQuery("search", "all") {
case "all":
ids, err := db.GerUsersIDByIDLike(keyword)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
scopes = append(scopes, db.WhereUsernameLikeOrIDIn(keyword, ids))
case "name":
scopes = append(scopes, db.WhereUsernameLike(keyword))
case "id":
ids, err := db.GerUsersIDByIDLike(keyword)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
scopes = append(scopes, db.WhereIDIn(ids))
}
}
scopes = append(scopes, func(db *gorm.DB) *gorm.DB {
return db.InnerJoins("JOIN room_members ON users.id = room_members.user_id AND room_members.room_id = ?", room.ID)
}, db.PreloadRoomMembers())
total, err := db.GetAllUserCount(scopes...)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
list, err := db.GetAllUsers(append(scopes, db.Paginate(page, pageSize))...)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": total,
"list": genRoomMemberListResp(list),
}))
}
func RoomAdminApproveMember(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
room := ctx.MustGet("room").(*op.RoomEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
var req model.RoomApproveMemberReq
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("decode room approve user req failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
err := user.ApproveRoomPendingMember(room, req.ID)
if err != nil {
log.Errorf("approve room user failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Status(http.StatusNoContent)
}
func RoomAdminBanMember(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
room := ctx.MustGet("room").(*op.RoomEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
var req model.RoomBanMemberReq
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("decode room ban user req failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
err := user.BanRoomMember(room, req.ID)
if err != nil {
log.Errorf("ban room user failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Status(http.StatusNoContent)
}
func RoomAdminUnbanMember(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
room := ctx.MustGet("room").(*op.RoomEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
var req model.RoomUnbanMemberReq
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("decode room unban user req failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
err := user.UnbanRoomMember(room, req.ID)
if err != nil {
log.Errorf("unban room user failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Status(http.StatusNoContent)
}
func RoomSetMemberPermissions(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
room := ctx.MustGet("room").(*op.RoomEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
var req model.RoomSetMemberPermissionsReq
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("decode room set user permissions req failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
err := user.SetMemberPermissions(room, req.ID, req.Permissions)
if err != nil {
log.Errorf("set room user permissions failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Status(http.StatusNoContent)
}
func RoomSetAdmin(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
room := ctx.MustGet("room").(*op.RoomEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
var req model.RoomSetAdminReq
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("decode room set admin req failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
err := user.SetRoomAdmin(room, req.ID, req.AdminPermissions)
if err != nil {
log.Errorf("set room admin failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Status(http.StatusNoContent)
}
func RoomSetMember(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
room := ctx.MustGet("room").(*op.RoomEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
var req model.RoomSetMemberReq
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("decode room set user req failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
err := user.SetRoomMember(room, req.ID, req.Permissions)
if err != nil {
log.Errorf("set room user failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Status(http.StatusNoContent)
}
func RoomSetAdminPermissions(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
room := ctx.MustGet("room").(*op.RoomEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
var req model.RoomSetAdminPermissionsReq
if err := model.Decode(ctx, &req); err != nil {
log.Errorf("decode room set admin permissions req failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
err := user.SetRoomAdminPermissions(room, req.ID, req.AdminPermissions)
if err != nil {
log.Errorf("set room admin permissions failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.Status(http.StatusNoContent)
}

@ -39,46 +39,46 @@ func GetPageItems[T any](ctx *gin.Context, items []T) ([]T, error) {
return utils.GetPageItems(items, page, max), nil
}
func MovieList(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
user := ctx.MustGet("user").(*op.UserEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
page, max, err := utils.GetPageAndMax(ctx)
if err != nil {
log.Errorf("get page and max error: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
// func MovieList(ctx *gin.Context) {
// room := ctx.MustGet("room").(*op.RoomEntry).Value()
// user := ctx.MustGet("user").(*op.UserEntry).Value()
// log := ctx.MustGet("log").(*logrus.Entry)
// page, max, err := utils.GetPageAndMax(ctx)
// if err != nil {
// log.Errorf("get page and max error: %v", err)
// ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
// return
// }
currentResp, err := genCurrentResp(ctx, user, room)
if err != nil {
log.Errorf("gen current resp error: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
// currentResp, err := genCurrentResp(ctx, user, room)
// if err != nil {
// log.Errorf("gen current resp error: %v", err)
// ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
// return
// }
m := room.GetMoviesWithPage(page, max)
mresp := make([]model.MovieResp, len(m))
for i, v := range m {
mresp[i] = model.MovieResp{
Id: v.Movie.ID,
Base: v.Movie.Base,
Creator: op.GetUserName(v.Movie.CreatorID),
}
// hide url and headers when proxy
if user.ID != v.Movie.CreatorID && v.Movie.Base.Proxy {
mresp[i].Base.Url = ""
mresp[i].Base.Headers = nil
}
}
// m := room.GetMoviesWithPage(page, max)
// mresp := make([]model.MovieResp, len(m))
// for i, v := range m {
// mresp[i] = model.MovieResp{
// Id: v.Movie.ID,
// Base: v.Movie.Base,
// Creator: op.GetUserName(v.Movie.CreatorID),
// }
// // hide url and headers when proxy
// if user.ID != v.Movie.CreatorID && v.Movie.Base.Proxy {
// mresp[i].Base.Url = ""
// mresp[i].Base.Headers = nil
// }
// }
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"current": currentResp,
"total": room.GetMoviesCount(),
"movies": mresp,
}))
}
// ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
// "current": currentResp,
// "total": room.GetMoviesCount(),
// "movies": mresp,
// }))
// }
func genCurrentResp(ctx context.Context, user *op.User, room *op.Room) (*model.CurrentMovieResp, error) {
return genCurrentRespWithCurrent(ctx, user, room, room.Current())
@ -88,7 +88,7 @@ func genCurrentMovieInfo(ctx context.Context, user *op.User, room *op.Room, opMo
if opMovie == nil || opMovie.ID == "" {
return &model.MovieResp{}, nil
}
var movie = opMovie.Movie
var movie = *opMovie.Movie
if movie.Base.VendorInfo.Vendor != "" {
vendorMovie, err := genVendorMovie(ctx, user, opMovie)
if err != nil {
@ -171,7 +171,7 @@ func Movies(ctx *gin.Context) {
return
}
m := room.GetMoviesWithPage(int(page), int(max))
m, total := user.GetRoomMoviesWithPage(room, int(page), int(max))
mresp := make([]*model.MovieResp, len(m))
for i, v := range m {
@ -188,7 +188,7 @@ func Movies(ctx *gin.Context) {
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": room.GetMoviesCount(),
"total": total,
"movies": mresp,
}))
}
@ -276,7 +276,7 @@ func NewPublishKey(ctx *gin.Context) {
return
}
if movie.Movie.CreatorID != user.ID && !user.HasRoomPermission(room, dbModel.PermissionEditUser) {
if movie.Movie.CreatorID != user.ID {
log.Errorf("new publish key error: %v", dbModel.ErrNoPermission)
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(dbModel.ErrNoPermission))
return
@ -1057,7 +1057,7 @@ func proxyVendorMovie(ctx *gin.Context, movie *op.Movie) {
// user is the api requester
func genVendorMovie(ctx context.Context, user *op.User, opMovie *op.Movie) (*dbModel.Movie, error) {
movie := opMovie.Movie
movie := *opMovie.Movie
var err error
switch movie.Base.VendorInfo.Vendor {
case dbModel.VendorBilibili:

@ -3,7 +3,6 @@ package handlers
import (
"context"
"errors"
"fmt"
"net/http"
"slices"
"time"
@ -29,10 +28,26 @@ var (
ErrRoomAlready = errors.New("room already exists")
)
type FormatErrNotSupportPosition string
func RoomMe(ctx *gin.Context) {
user := ctx.MustGet("user").(*op.UserEntry).Value()
room := ctx.MustGet("room").(*op.RoomEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
rur, err := room.LoadOrCreateRoomMember(user.ID)
if err != nil {
log.Errorf("room me failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
func (e FormatErrNotSupportPosition) Error() string {
return fmt.Sprintf("not support position %s", string(e))
ctx.JSON(http.StatusOK, model.NewApiDataResp(&model.RoomMeResp{
UserID: user.ID,
RoomID: room.ID,
JoinAt: rur.CreatedAt.UnixMilli(),
Role: rur.Role,
Permissions: rur.Permissions,
AdminPermissions: rur.AdminPermissions,
}))
}
func CreateRoom(ctx *gin.Context) {
@ -52,7 +67,7 @@ func CreateRoom(ctx *gin.Context) {
return
}
room, err := user.CreateRoom(req.RoomName, req.Password, db.WithSetting(req.Setting))
room, err := user.CreateRoom(req.RoomName, req.Password, db.WithSettingHidden(req.Hidden))
if err != nil {
log.Errorf("create room failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
@ -72,7 +87,7 @@ func CreateRoom(ctx *gin.Context) {
}))
}
var roomHotCache = refreshcache.NewRefreshCache[[]*model.RoomListResp](func(context.Context, ...any) ([]*model.RoomListResp, error) {
var roomHotCache = refreshcache.NewRefreshCache(func(context.Context, ...any) ([]*model.RoomListResp, error) {
rooms := make([]*model.RoomListResp, 0)
op.RangeRoomCache(func(key string, value *synccache.Entry[*op.Room]) bool {
v := value.Value()
@ -145,6 +160,9 @@ func RoomList(ctx *gin.Context) {
var desc = ctx.DefaultQuery("order", "desc") == "desc"
scopes := []func(db *gorm.DB) *gorm.DB{
func(db *gorm.DB) *gorm.DB {
return db.InnerJoins("JOIN room_settings ON rooms.id = room_settings.id")
},
db.WhereRoomSettingWithoutHidden(),
db.WhereStatus(dbModel.RoomStatusActive),
}
@ -172,24 +190,53 @@ func RoomList(ctx *gin.Context) {
// search mode, all, name, creator
switch ctx.DefaultQuery("search", "all") {
case "all":
scopes = append(scopes, db.WhereRoomNameLikeOrCreatorInOrIDLike(keyword, db.GerUsersIDByUsernameLike(keyword), keyword))
ids, err := db.GerUsersIDByUsernameLike(keyword)
if err != nil {
log.Errorf("get room list failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
scopes = append(scopes, db.WhereRoomNameLikeOrCreatorInOrIDLike(keyword, ids, keyword))
case "name":
scopes = append(scopes, db.WhereRoomNameLike(keyword))
case "creator":
scopes = append(scopes, db.WhereCreatorIDIn(db.GerUsersIDByUsernameLike(keyword)))
ids, err := db.GerUsersIDByUsernameLike(keyword)
if err != nil {
log.Errorf("get room list failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
scopes = append(scopes, db.WhereCreatorIDIn(ids))
case "id":
scopes = append(scopes, db.WhereIDLike(keyword))
}
}
total, err := db.GetAllRoomsCount(scopes...)
if err != nil {
log.Errorf("get room list failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
list, err := genRoomListResp(append(scopes, db.Paginate(page, pageSize))...)
if err != nil {
log.Errorf("get room list failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": db.GetAllRoomsCount(scopes...),
"list": genRoomListResp(append(scopes, db.Paginate(page, pageSize))...),
"total": total,
"list": list,
}))
}
func genRoomListResp(scopes ...func(db *gorm.DB) *gorm.DB) []*model.RoomListResp {
rs := db.GetAllRooms(scopes...)
func genRoomListResp(scopes ...func(db *gorm.DB) *gorm.DB) ([]*model.RoomListResp, error) {
rs, err := db.GetAllRooms(scopes...)
if err != nil {
return nil, err
}
resp := make([]*model.RoomListResp, len(rs))
for i, r := range rs {
resp[i] = &model.RoomListResp{
@ -203,7 +250,7 @@ func genRoomListResp(scopes ...func(db *gorm.DB) *gorm.DB) []*model.RoomListResp
Status: r.Status,
}
}
return resp
return resp, nil
}
func CheckRoom(ctx *gin.Context) {
@ -336,7 +383,7 @@ func SetRoomSetting(ctx *gin.Context) {
return
}
if err := user.SetRoomSetting(room, dbModel.RoomSettings(req)); err != nil {
if err := user.UpdateRoomSettings(room, req); err != nil {
log.Errorf("set room setting failed: %v", err)
if errors.Is(err, dbModel.ErrNoPermission) {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorResp(err))
@ -348,66 +395,3 @@ func SetRoomSetting(ctx *gin.Context) {
ctx.Status(http.StatusNoContent)
}
func RoomUsers(ctx *gin.Context) {
room := ctx.MustGet("room").(*op.RoomEntry).Value()
log := ctx.MustGet("log").(*logrus.Entry)
page, pageSize, err := utils.GetPageAndMax(ctx)
if err != nil {
log.Errorf("get room users failed: %v", err)
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorResp(err))
return
}
var desc = ctx.DefaultQuery("order", "desc") == "desc"
preloadScopes := []func(db *gorm.DB) *gorm.DB{db.WhereRoomID(room.ID)}
scopes := []func(db *gorm.DB) *gorm.DB{}
switch ctx.DefaultQuery("status", "active") {
case "pending":
preloadScopes = append(preloadScopes, db.WhereRoomUserStatus(dbModel.RoomUserStatusPending))
case "banned":
preloadScopes = append(preloadScopes, db.WhereRoomUserStatus(dbModel.RoomUserStatusBanned))
case "active":
preloadScopes = append(preloadScopes, db.WhereRoomUserStatus(dbModel.RoomUserStatusActive))
}
switch ctx.DefaultQuery("sort", "name") {
case "join":
if desc {
preloadScopes = append(preloadScopes, db.OrderByCreatedAtDesc)
} else {
preloadScopes = append(preloadScopes, db.OrderByCreatedAtAsc)
}
case "name":
if desc {
scopes = append(scopes, db.OrderByDesc("username"))
} else {
scopes = append(scopes, db.OrderByAsc("username"))
}
default:
log.Errorf("get room users failed: not support sort")
ctx.AbortWithStatusJSON(http.StatusBadRequest, model.NewApiErrorStringResp("not support sort"))
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)))
}
}
scopes = append(scopes, db.PreloadRoomUserRelations(preloadScopes...))
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": db.GetAllUserCount(scopes...),
"list": genRoomUserListResp(db.GetAllUsers(append(scopes, db.Paginate(page, pageSize))...)),
}))
}

@ -138,7 +138,13 @@ func UserRooms(ctx *gin.Context) {
// search mode, all, name, creator
switch ctx.DefaultQuery("search", "all") {
case "all":
scopes = append(scopes, db.WhereRoomNameLikeOrCreatorInOrIDLike(keyword, db.GerUsersIDByUsernameLike(keyword), keyword))
ids, err := db.GerUsersIDByUsernameLike(keyword)
if err != nil {
log.Errorf("failed to get all rooms count: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
scopes = append(scopes, db.WhereRoomNameLikeOrCreatorInOrIDLike(keyword, ids, keyword))
case "name":
scopes = append(scopes, db.WhereRoomNameLike(keyword))
case "id":
@ -146,9 +152,23 @@ func UserRooms(ctx *gin.Context) {
}
}
total, err := db.GetAllRoomsCount(scopes...)
if err != nil {
log.Errorf("failed to get all rooms count: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
list, err := genRoomListResp(append(scopes, db.Paginate(page, pageSize))...)
if err != nil {
log.Errorf("failed to get all rooms: %v", err)
ctx.AbortWithStatusJSON(http.StatusInternalServerError, model.NewApiErrorResp(err))
return
}
ctx.JSON(http.StatusOK, model.NewApiDataResp(gin.H{
"total": db.GetAllRoomsCount(scopes...),
"list": genRoomListResp(append(scopes, db.Paginate(page, pageSize))...),
"total": total,
"list": list,
}))
}

@ -1,6 +1,7 @@
package handlers
import (
"errors"
"io"
"net/http"
"time"
@ -9,6 +10,7 @@ import (
"github.com/gorilla/websocket"
"github.com/sirupsen/logrus"
log "github.com/sirupsen/logrus"
dbModel "github.com/synctv-org/synctv/internal/model"
"github.com/synctv-org/synctv/internal/op"
pb "github.com/synctv-org/synctv/proto/message"
"github.com/synctv-org/synctv/server/middlewares"
@ -133,7 +135,7 @@ func handleReaderMessage(c *op.Client, l *logrus.Entry) error {
}
l.Debugf("ws: receive message: %v", msg.String())
if err = handleElementMsg(c, &msg, l); err != nil {
if err = handleElementMsg(c, &msg); err != nil {
l.Errorf("ws: handle message error: %v", err)
return err
}
@ -142,7 +144,7 @@ func handleReaderMessage(c *op.Client, l *logrus.Entry) error {
const MaxChatMessageLength = 4096
func handleElementMsg(cli *op.Client, msg *pb.ElementMessage, l *logrus.Entry) error {
func handleElementMsg(cli *op.Client, msg *pb.ElementMessage) error {
var timeDiff float64
if msg.Time != 0 {
timeDiff = time.Since(time.UnixMilli(msg.Time)).Seconds()
@ -163,20 +165,24 @@ func handleElementMsg(cli *op.Client, msg *pb.ElementMessage, l *logrus.Entry) e
Error: "message too long",
})
}
return cli.Broadcast(&pb.ElementMessage{
Type: pb.ElementMessageType_CHAT_MESSAGE,
ChatResp: &pb.ChatResp{
Sender: &pb.Sender{
Username: cli.User().Username,
Userid: cli.User().ID,
},
Message: message,
},
})
err := cli.SendChatMessage(message)
if err != nil && errors.Is(err, dbModel.ErrNoPermission) {
return cli.Send(&pb.ElementMessage{
Type: pb.ElementMessageType_ERROR,
Error: err.Error(),
})
}
return err
case pb.ElementMessageType_PLAY,
pb.ElementMessageType_PAUSE,
pb.ElementMessageType_CHANGE_RATE:
status := cli.Room().SetStatus(msg.ChangeMovieStatusReq.Playing, msg.ChangeMovieStatusReq.Seek, msg.ChangeMovieStatusReq.Rate, timeDiff)
status, err := cli.SetStatus(msg.ChangeMovieStatusReq.Playing, msg.ChangeMovieStatusReq.Seek, msg.ChangeMovieStatusReq.Rate, timeDiff)
if err != nil {
return cli.Send(&pb.ElementMessage{
Type: pb.ElementMessageType_ERROR,
Error: err.Error(),
})
}
return cli.Broadcast(&pb.ElementMessage{
Type: msg.Type,
MovieStatusChanged: &pb.MovieStatusChanged{
@ -192,7 +198,13 @@ func handleElementMsg(cli *op.Client, msg *pb.ElementMessage, l *logrus.Entry) e
},
}, op.WithIgnoreClient(cli))
case pb.ElementMessageType_CHANGE_SEEK:
status := cli.Room().SetSeekRate(msg.ChangeMovieStatusReq.Seek, msg.ChangeMovieStatusReq.Rate, timeDiff)
status, err := cli.SetSeekRate(msg.ChangeMovieStatusReq.Seek, msg.ChangeMovieStatusReq.Rate, timeDiff)
if err != nil {
return cli.Send(&pb.ElementMessage{
Type: pb.ElementMessageType_ERROR,
Error: err.Error(),
})
}
return cli.Broadcast(&pb.ElementMessage{
Type: msg.Type,
MovieStatusChanged: &pb.MovieStatusChanged{

@ -2,6 +2,7 @@ package middlewares
import (
"errors"
"fmt"
"net/http"
"strings"
"time"
@ -93,6 +94,17 @@ func AuthRoom(Authorization string) (*op.UserEntry, *op.RoomEntry, error) {
return nil, nil, ErrAuthExpired
}
rus, err := r.Value().UserStatus(u.Value().ID)
if err != nil {
return nil, nil, err
}
if !rus.IsActive() {
if rus.IsPending() {
return nil, nil, fmt.Errorf("user is pending, need admin to approve")
}
return nil, nil, fmt.Errorf("user is banned")
}
return u, r, nil
}
@ -155,10 +167,10 @@ func NewAuthRoomToken(user *op.User, room *op.Room) (string, error) {
return "", errors.New("room is pending, need admin to approve")
}
if room.Settings.DisableJoinNewUser {
if _, err := room.GetRoomUserRelation(user.ID); err != nil {
if _, err := room.GetRoomMemberPermission(user.ID); err != nil {
return "", errors.New("room is not allow new user to join")
}
} else if _, err := room.LoadOrCreateRoomUserRelation(user.ID); err != nil {
} else if _, err := room.LoadOrCreateRoomMember(user.ID); err != nil {
return "", err
}
@ -257,6 +269,36 @@ func AuthRoomMiddleware(ctx *gin.Context) {
log.Data["uro"] = user.Role.String()
}
func AuthRoomAdminMiddleware(ctx *gin.Context) {
AuthRoomMiddleware(ctx)
if ctx.IsAborted() {
return
}
room := ctx.MustGet("room").(*synccache.Entry[*op.Room]).Value()
user := ctx.MustGet("user").(*synccache.Entry[*op.User]).Value()
if !user.IsRoomAdmin(room) {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("user has no permission"))
return
}
}
func AuthRoomCreatorMiddleware(ctx *gin.Context) {
AuthRoomMiddleware(ctx)
if ctx.IsAborted() {
return
}
room := ctx.MustGet("room").(*synccache.Entry[*op.Room]).Value()
user := ctx.MustGet("user").(*synccache.Entry[*op.User]).Value()
if room.CreatorID != user.ID {
ctx.AbortWithStatusJSON(http.StatusForbidden, model.NewApiErrorStringResp("user is not creator"))
return
}
}
func AuthAdminMiddleware(ctx *gin.Context) {
AuthUserMiddleware(ctx)
if ctx.IsAborted() {

@ -0,0 +1,48 @@
package model
import (
dbModel "github.com/synctv-org/synctv/internal/model"
)
type RoomMembersResp struct {
UserID string `json:"userId"`
Username string `json:"username"`
JoinAt int64 `json:"joinAt"`
Role dbModel.RoomMemberRole `json:"role"`
RoomID string `json:"roomId"`
Permissions dbModel.RoomMemberPermission `json:"permissions"`
AdminPermissions dbModel.RoomAdminPermission `json:"adminPermissions"`
}
type RoomApproveMemberReq = UserIDReq
type RoomBanMemberReq = UserIDReq
type RoomUnbanMemberReq = UserIDReq
type RoomSetMemberPermissionsReq struct {
UserIDReq
Permissions dbModel.RoomMemberPermission `json:"permissions"`
}
type RoomMeResp struct {
UserID string `json:"userId"`
RoomID string `json:"roomId"`
JoinAt int64 `json:"joinAt"`
Role dbModel.RoomMemberRole `json:"role"`
Permissions dbModel.RoomMemberPermission `json:"permissions"`
AdminPermissions dbModel.RoomAdminPermission `json:"adminPermissions"`
}
type RoomSetAdminReq struct {
UserIDReq
AdminPermissions dbModel.RoomAdminPermission `json:"adminPermissions"`
}
type RoomSetMemberReq struct {
UserIDReq
Permissions dbModel.RoomMemberPermission `json:"permissions"`
}
type RoomSetAdminPermissionsReq struct {
UserIDReq
AdminPermissions dbModel.RoomAdminPermission `json:"adminPermissions"`
}

@ -8,7 +8,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/synctv-org/synctv/internal/model"
dbModel "github.com/synctv-org/synctv/internal/model"
)
var (
@ -27,9 +26,9 @@ func (f FormatEmptyPasswordError) Error() string {
}
type CreateRoomReq struct {
RoomName string `json:"roomName"`
Password string `json:"password"`
Setting dbModel.RoomSettings `json:"setting"`
RoomName string `json:"roomName"`
Password string `json:"password"`
Hidden bool `json:"hidden"`
}
func (c *CreateRoomReq) Decode(ctx *gin.Context) error {
@ -121,7 +120,7 @@ func (r *RoomIDReq) Validate() error {
return nil
}
type SetRoomSettingReq dbModel.RoomSettings
type SetRoomSettingReq map[string]any
func (s *SetRoomSettingReq) Decode(ctx *gin.Context) error {
return json.NewDecoder(ctx.Request.Body).Decode(s)
@ -130,13 +129,3 @@ func (s *SetRoomSettingReq) Decode(ctx *gin.Context) error {
func (s *SetRoomSettingReq) Validate() error {
return nil
}
type RoomUsersResp struct {
UserID string `json:"userId"`
Username string `json:"username"`
Role dbModel.Role `json:"role"`
JoinAt int64 `json:"joinAt"`
RoomID string `json:"roomId"`
Status dbModel.RoomUserStatus `json:"status"`
Permissions dbModel.RoomUserPermission `json:"permissions"`
}

Loading…
Cancel
Save