live-sfu-demo/internal/room/service.go

264 lines
7.4 KiB
Go

package room
import (
"context"
"database/sql"
"errors"
)
type Service struct {
store *Store
}
func NewService(store *Store) *Service { return &Service{store: store} }
func (s *Service) Store() *Store { return s.store }
func (s *Service) GetRoom(ctx context.Context, name string) (*Room, error) {
if s.store == nil || s.store.db == nil {
return nil, errors.New("room store not configured")
}
return s.store.GetRoom(ctx, name)
}
func (s *Service) CreateRoom(ctx context.Context, name, creator string, settings *RoomSettings) (*Room, error) {
if name == "" {
return nil, errors.New("room name required")
}
if creator == "" {
return nil, errors.New("creator required")
}
r := NewRoom(name, creator)
if settings != nil {
r.Settings = settings
r.IsPublic = settings.IsPublic
r.RequireApproval = settings.RequireApproval
if settings.MaxMembers > 0 {
r.MaxMembers = settings.MaxMembers
}
}
if err := s.store.CreateRoom(ctx, r); err != nil {
return nil, err
}
creatorMember := NewMember(name, creator, RoleCreator)
if err := s.store.UpsertMember(ctx, creatorMember); err != nil {
return nil, err
}
return r, nil
}
func (s *Service) JoinRoom(ctx context.Context, roomName, username string, password string) (*RoomMember, error) {
r, err := s.store.GetRoom(ctx, roomName)
if err != nil {
return nil, err
}
if r.Status == RoomBanned {
return nil, errors.New("room is banned")
}
if r.Status == RoomClosed {
return nil, errors.New("room is closed")
}
if r.PasswordHash != "" {
if !verifyPassword(password, r.PasswordHash) {
return nil, errors.New("invalid room password")
}
}
if cnt, _ := s.store.CountMembers(ctx, roomName); cnt >= r.MaxMembers && r.MaxMembers > 0 {
return nil, errors.New("room is full")
}
member, err := s.store.GetMember(ctx, roomName, username)
if err == nil && member.Status == MemberActive {
return member, nil
}
if err == nil && member.Status == MemberBanned {
return nil, errors.New("you are banned from this room")
}
newMember := NewMember(roomName, username, RoleMember)
if s.store != nil {
_ = s.store.UpsertMember(ctx, newMember)
}
return newMember, nil
}
func (s *Service) GetMember(ctx context.Context, room, userID string) (*RoomMember, error) {
return s.store.GetMember(ctx, room, userID)
}
func (s *Service) EnsurePermission(ctx context.Context, room, userID string, perm RoomPermission) error {
if s.store == nil || s.store.db == nil {
return nil
}
r, err := s.store.GetRoom(ctx, room)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return errors.New("room not found")
}
return err
}
if r.Status == RoomBanned {
return errors.New("room is banned")
}
if r.Status == RoomClosed && perm != PermViewMembers && perm != PermViewChatHistory {
return errors.New("room is closed")
}
m, err := s.store.GetMember(ctx, room, userID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
if r.IsPublic || r.Settings.AllowGuestJoin {
guest := NewMember(room, userID, RoleGuest)
if guest.HasPermission(perm, r.Settings) {
return nil
}
}
return errors.New("not a member of this room")
}
return err
}
if m.Status != MemberActive {
return errors.New("member not active")
}
if !m.HasPermission(perm, r.Settings) {
return errors.New("permission denied: " + perm.String())
}
return nil
}
func (s *Service) CheckPermission(ctx context.Context, room, userID string, perm RoomPermission) bool {
return s.EnsurePermission(ctx, room, userID, perm) == nil
}
func (s *Service) ListMembers(ctx context.Context, room string) ([]*RoomMember, error) {
return s.store.ListMembers(ctx, room)
}
func (s *Service) UpdateMemberRole(ctx context.Context, room, actorID, targetID string, newRole RoomRole) error {
if newRole == RoleCreator {
return errors.New("cannot assign creator role")
}
roomObj, err := s.store.GetRoom(ctx, room)
if err != nil {
return err
}
actor, err := s.store.GetMember(ctx, room, actorID)
if err != nil {
return errors.New("actor not in room")
}
if !actor.HasPermission(PermManageMembers, roomObj.Settings) {
return errors.New("permission denied: manage members")
}
if !actor.Role.CanManage(newRole) {
return errors.New("cannot manage role higher than yours")
}
target, err := s.store.GetMember(ctx, room, targetID)
if err != nil {
return errors.New("target not in room")
}
if target.Role == RoleCreator {
return errors.New("cannot change creator role")
}
if !actor.Role.CanManage(target.Role) {
return errors.New("cannot manage member with higher or equal role")
}
target.Role = newRole
return s.store.UpsertMember(ctx, target)
}
func (s *Service) UpdateMemberPermissions(ctx context.Context, room, actorID, targetID string, added, removed uint64, isAdmin bool) error {
roomObj, err := s.store.GetRoom(ctx, room)
if err != nil {
return err
}
actor, err := s.store.GetMember(ctx, room, actorID)
if err != nil {
return errors.New("actor not in room")
}
if !actor.HasPermission(PermManagePermissions, roomObj.Settings) {
return errors.New("permission denied: manage permissions")
}
target, err := s.store.GetMember(ctx, room, targetID)
if err != nil {
return errors.New("target not in room")
}
if !actor.Role.CanManage(target.Role) {
return errors.New("cannot manage this member")
}
if isAdmin {
if target.Role != RoleAdmin && target.Role != RoleCreator {
return errors.New("admin permissions only for admin/creator")
}
target.AdminAdded = added
target.AdminRemoved = removed
} else {
target.AddedPerms = added
target.RemovedPerms = removed
}
return s.store.UpsertMember(ctx, target)
}
func (s *Service) KickMember(ctx context.Context, room, actorID, targetID string) error {
roomObj, err := s.store.GetRoom(ctx, room)
if err != nil {
return err
}
actor, err := s.store.GetMember(ctx, room, actorID)
if err != nil {
return errors.New("actor not in room")
}
if !actor.HasPermission(PermRemoveMembers, roomObj.Settings) {
return errors.New("permission denied: remove members")
}
target, err := s.store.GetMember(ctx, room, targetID)
if err != nil {
return errors.New("target not in room")
}
if target.Role == RoleCreator {
return errors.New("cannot kick creator")
}
if !actor.Role.CanManage(target.Role) {
return errors.New("cannot kick higher role")
}
return s.store.RemoveMember(ctx, room, targetID)
}
func (s *Service) BanMember(ctx context.Context, room, actorID, targetID string) error {
if err := s.KickMember(ctx, room, actorID, targetID); err != nil {
return err
}
banned := NewMember(room, targetID, RoleGuest)
banned.Status = MemberBanned
return s.store.UpsertMember(ctx, banned)
}
func (s *Service) LeaveRoom(ctx context.Context, room, userID string) error {
m, err := s.store.GetMember(ctx, room, userID)
if err != nil {
return err
}
if m.Role == RoleCreator {
return errors.New("creator cannot leave, transfer ownership or delete room")
}
return s.store.RemoveMember(ctx, room, userID)
}
func (s *Service) UpdateRoomSettings(ctx context.Context, room, actorID string, newSettings *RoomSettings) error {
r, err := s.store.GetRoom(ctx, room)
if err != nil {
return err
}
actor, err := s.store.GetMember(ctx, room, actorID)
if err != nil {
return errors.New("actor not in room")
}
if !actor.HasPermission(PermManageRoom, r.Settings) {
return errors.New("permission denied: manage room")
}
return s.store.UpdateRoomSettings(ctx, room, newSettings)
}
func verifyPassword(input, hash string) bool {
if hash == "" {
return true
}
return input == hash
}