package roomserver import ( "context" "github.com/dariuszjaniak/warp/server/internal/matrix/event" "github.com/dariuszjaniak/warp/server/internal/storage" ) // RoomDetails is a room as the admin panel shows it: the stored summary // plus the things only a count can answer. type RoomDetails struct { storage.RoomSummary // Members counts every m.room.member event in the current state, // whatever the membership — a room with one member and forty bans is // not the same room as one with one member. Members int // InvitedMembers counts the outstanding invites. InvitedMembers int // Aliases are the local aliases pointing at the room. Aliases []string // ForwardExtremities are the event IDs the room's DAG currently ends // at. There is normally one; more than one means the room has forked, // which is worth seeing on an operator's screen. ForwardExtremities []string } // ListRooms returns a page of every room on this server, for the admin API. func (r *API) ListRooms(ctx context.Context, filter storage.RoomFilter) ([]storage.RoomSummary, int, error) { return r.store.ListRooms(ctx, filter) } // RoomDetails reads one room in full, for the admin API. func (r *API) RoomDetails(ctx context.Context, roomID string) (RoomDetails, error) { summary, err := r.store.RoomSummary(ctx, roomID) if err != nil { return RoomDetails{}, err } memberships, err := r.store.MembershipEntries(ctx, roomID, nil) if err != nil { return RoomDetails{}, err } details := RoomDetails{RoomSummary: summary, Members: len(memberships)} for _, m := range memberships { if m.Membership == event.MembershipInvite { details.InvitedMembers++ } } if details.Aliases, err = r.store.AliasesForRoom(ctx, roomID); err != nil { return RoomDetails{}, err } if details.Aliases == nil { details.Aliases = []string{} } extremities, err := r.store.Extremities(ctx, roomID) if err != nil { return RoomDetails{}, err } details.ForwardExtremities = make([]string, 0, len(extremities)) for _, e := range extremities { details.ForwardExtremities = append(details.ForwardExtremities, e.EventID) } return details, nil } // PurgeRoom deletes a room and everything belonging to it. // // This is the one operation in the roomserver that destroys history, and it // is irreversible: the events are gone, not tombstoned. It exists because a // room can become something an operator has to be able to remove — abuse, a // room nobody is in any more, a room whose version this build no longer // supports — and because the alternative an operator reaches for otherwise // is deleting rows by hand. // // It does not tell the room's members. Nothing can: the room's events are // what a membership change would have to be added to. func (r *API) PurgeRoom(ctx context.Context, roomID string) error { // The lock is taken so that a purge cannot race a send into the same // room and leave the event pointing at a room row that is gone. mu := r.lock(roomID) mu.Lock() defer mu.Unlock() if err := r.store.PurgeRoom(ctx, roomID); err != nil { return err } // The lock itself is dropped: a purged room ID will not come back, and // keeping its mutex would make the map grow with every purge. r.locks.Delete(roomID) r.log.Warn("purged room", "room_id", roomID) return nil } // Stats returns the aggregate room counts for the admin dashboard. func (r *API) Stats(ctx context.Context) (storage.RoomStats, error) { return r.store.RoomStats(ctx) }