264 lines
7.4 KiB
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
|
|
}
|