package room import ( "context" "database/sql" "errors" "golang.org/x/crypto/bcrypt" ) 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 err := verifyRoomPassword(password, r.PasswordHash); err != nil { return nil, err } } 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") } if err == nil && member.Status == MemberPending { return nil, errors.New("join request pending approval") } newMember := NewMember(roomName, username, RoleMember) if r.RequireApproval { newMember.Status = MemberPending } 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) } // HashRoomPassword 使用 bcrypt 哈希房间密码。 func HashRoomPassword(password string) (string, error) { if password == "" { return "", nil } b, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return "", err } return string(b), nil } func verifyRoomPassword(input, hash string) error { if hash == "" { return nil } if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(input)); err != nil { return errors.New("invalid room password") } return nil } // ApproveJoin 将 pending 成员升级为 active member,需 PermManageMembers 权限。 func (s *Service) ApproveJoin(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(PermManageMembers, roomObj.Settings) { return errors.New("permission denied: manage members") } target, err := s.store.GetMember(ctx, room, targetID) if err != nil { return errors.New("target not found") } if target.Status != MemberPending { return errors.New("member is not pending approval") } target.Status = MemberActive return s.store.UpsertMember(ctx, target) } // RejectJoin 删除一个 pending 成员的加入请求。 func (s *Service) RejectJoin(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(PermManageMembers, roomObj.Settings) { return errors.New("permission denied: manage members") } target, err := s.store.GetMember(ctx, room, targetID) if err != nil { return errors.New("target not found") } if target.Status != MemberPending { return errors.New("member is not pending approval") } return s.store.RemoveMember(ctx, room, targetID) } // TransferOwnership 将房间所有权从当前 creator 转移给目标 active 成员。 func (s *Service) TransferOwnership(ctx context.Context, roomName, actorID, targetID string) error { r, err := s.store.GetRoom(ctx, roomName) if err != nil { return err } _ = r actor, err := s.store.GetMember(ctx, roomName, actorID) if err != nil { return errors.New("actor not in room") } if actor.Role != RoleCreator { return errors.New("only creator can transfer ownership") } target, err := s.store.GetMember(ctx, roomName, targetID) if err != nil { return errors.New("target not in room") } if target.Status != MemberActive { return errors.New("target must be an active member") } if target.Role == RoleCreator { return errors.New("target is already the creator") } // 更新房间表的 creator 字段 if err := s.store.UpdateRoomCreator(ctx, roomName, targetID); err != nil { return err } // 升级 target 为 creator target.Role = RoleCreator if err := s.store.UpsertMember(ctx, target); err != nil { return err } // 降级 actor 为 admin actor.Role = RoleAdmin return s.store.UpsertMember(ctx, actor) }