Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion clientapi/routing/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func AdminCreateNewRegistrationToken(req *http.Request, cfg *config.ClientAPI, u
}

if len(token) > 64 {
//Token present in request body, but is too long.
// Token present in request body, but is too long.
return util.JSONResponse{
Code: http.StatusBadRequest,
JSON: spec.BadJSON("token must not be longer than 64"),
Expand Down Expand Up @@ -578,6 +578,23 @@ func DeleteEventReport(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAP
}
}

func QueryEmptyRooms(req *http.Request, rsAPI roomserverAPI.ClientRoomserverAPI) util.JSONResponse {
emptyRooms, err := rsAPI.AdminQueryEmptyRooms(req.Context())
if err != nil {
logrus.WithError(err).Error("failed to query empty rooms")
return util.JSONResponse{
Code: http.StatusInternalServerError,
JSON: spec.InternalServerError{},
}
}
return util.JSONResponse{
Code: http.StatusOK,
JSON: map[string]any{
"empty_rooms": emptyRooms,
},
}
}

func parseUint64OrDefault(input string, defaultValue uint64) uint64 {
v, err := strconv.ParseUint(input, 10, 64)
if err != nil {
Expand Down
6 changes: 6 additions & 0 deletions clientapi/routing/routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,12 @@ func Setup(
}),
).Methods(http.MethodPost, http.MethodOptions)

dendriteAdminRouter.Handle("/admin/emptyRooms",
httputil.MakeAdminAPI("admin_empty_rooms", userAPI, func(req *http.Request, device *userapi.Device) util.JSONResponse {
return QueryEmptyRooms(req, rsAPI)
}),
).Methods(http.MethodGet, http.MethodOptions)

// server notifications
if cfg.Matrix.ServerNotices.Enabled {
logrus.Info("Enabling server notices at /_synapse/admin/v1/send_server_notice")
Expand Down
13 changes: 13 additions & 0 deletions docs/administration/4_adminapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,19 @@ This endpoint instructs Dendrite to immediately query `/devices/{userID}` on a f

This endpoint instructs Dendrite to remove the given room from its database. It does **NOT** remove media files. Depending on the size of the room, this may take a while. Will return an empty JSON once other components were instructed to delete the room.

## GET `/_dendrite/admin/emptyRooms`

Returns a list of all rooms which have zero (locally) joined members. Response format:

```json
{
"empty_rooms": [
"!roomid1:server_name",
"!roomid2:server_name"
]
}
```

## POST `/_synapse/admin/v1/send_server_notice`

Request body format:
Expand Down
5 changes: 4 additions & 1 deletion roomserver/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ type RoomserverInternalAPI interface {

// RoomsWithACLs returns all room IDs for rooms with ACLs
RoomsWithACLs(ctx context.Context) ([]string, error)
// EmptyRooms returns all rooms that the local server has left.
EmptyRooms(ctx context.Context) ([]string, error)
}

type UserRoomPrivateKeyCreator interface {
Expand Down Expand Up @@ -248,6 +250,7 @@ type ClientRoomserverAPI interface {
PerformAdminEvacuateUser(ctx context.Context, userID string) (affected []string, err error)
PerformAdminPurgeRoom(ctx context.Context, roomID string) error
PerformAdminDownloadState(ctx context.Context, roomID, userID string, serverName spec.ServerName) error
AdminQueryEmptyRooms(ctx context.Context) ([]string, error)
PerformPeek(ctx context.Context, req *PerformPeekRequest) (roomID string, err error)
PerformUnpeek(ctx context.Context, roomID, userID, deviceID string) error
PerformInvite(ctx context.Context, req *PerformInviteRequest) error
Expand All @@ -263,7 +266,7 @@ type ClientRoomserverAPI interface {
// If true, then the alias has not been set to the provided room, as it already in use.
SetRoomAlias(ctx context.Context, senderID spec.SenderID, roomID spec.RoomID, alias string) (aliasAlreadyExists bool, err error)

//RemoveRoomAlias(ctx context.Context, req *RemoveRoomAliasRequest, res *RemoveRoomAliasResponse) error
// RemoveRoomAlias(ctx context.Context, req *RemoveRoomAliasRequest, res *RemoveRoomAliasResponse) error
// Removes a room alias, as provided sender.
//
// Returns whether the alias was found, whether it was removed, and an error (if any occurred)
Expand Down
4 changes: 4 additions & 0 deletions roomserver/internal/perform/perform_admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,7 @@ func (r *Admin) PerformAdminDownloadState(
func (r *Admin) PerformAdminDeleteEventReport(ctx context.Context, reportID uint64) error {
return r.DB.AdminDeleteEventReport(ctx, reportID)
}

func (r *Admin) AdminQueryEmptyRooms(ctx context.Context) ([]string, error) {
return r.DB.EmptyRooms(ctx)
}
5 changes: 5 additions & 0 deletions roomserver/internal/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,11 @@ func (r *Queryer) RoomsWithACLs(ctx context.Context) ([]string, error) {
return r.DB.RoomsWithACLs(ctx)
}

// EmptyRooms returns all rooms that the local server has left.
func (r *Queryer) EmptyRooms(ctx context.Context) ([]string, error) {
return r.DB.EmptyRooms(ctx)
}

// QueryAdminEventReports returns event reports given a filter.
func (r *Queryer) QueryAdminEventReports(ctx context.Context, from uint64, limit uint64, backwards bool, userID, roomID string) ([]api.QueryAdminEventReportsResponse, int64, error) {
return r.DB.QueryAdminEventReports(ctx, from, limit, backwards, userID, roomID)
Expand Down
32 changes: 32 additions & 0 deletions roomserver/roomserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1319,3 +1319,35 @@ func TestRoomsWithACLs(t *testing.T) {
assert.Equal(t, []string{aclRoom.ID}, roomsWithACLs)
})
}

func TestEmptyRooms(t *testing.T) {
ctx := context.Background()
alice := test.NewUser(t)
r1 := test.NewRoom(t, alice)
r2 := test.NewRoom(t, alice)

r2.CreateAndInsert(t, alice, spec.MRoomMember, map[string]interface{}{"membership": spec.Leave}, test.WithStateKey(alice.ID))

test.WithAllDatabases(t, func(t *testing.T, dbType test.DBType) {
cfg, processCtx, closeDB := testrig.CreateConfig(t, dbType)
defer closeDB()

cm := sqlutil.NewConnectionManager(processCtx, cfg.Global.DatabaseOptions)
natsInstance := &jetstream.NATSInstance{}
caches := caching.NewRistrettoCache(128*1024*1024, time.Hour, caching.DisableMetrics)
// start JetStream listeners
rsAPI := roomserver.NewInternalAPI(processCtx, cfg, cm, natsInstance, caches, caching.DisableMetrics)
rsAPI.SetFederationAPI(nil, nil)

for _, room := range []*test.Room{r1, r2} {
// Create the rooms
err := api.SendEvents(ctx, rsAPI, api.KindNew, room.Events(), "test", "test", "test", nil, false)
assert.NoError(t, err)
}

// We should only have r2 as an empty room
emptyRooms, err := rsAPI.EmptyRooms(ctx)
assert.NoError(t, err)
assert.Equal(t, []string{r2.ID}, emptyRooms)
})
}
3 changes: 3 additions & 0 deletions roomserver/storage/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ type Database interface {

// RoomsWithACLs returns all room IDs for rooms with ACLs
RoomsWithACLs(ctx context.Context) ([]string, error)

// EmptyRooms returns all rooms that the local server has left.
EmptyRooms(ctx context.Context) ([]string, error)
// GetBulkStateACLs returns all server ACLs for the given rooms.
GetBulkStateACLs(ctx context.Context, roomIDs []string) ([]tables.StrippedEvent, error)
QueryAdminEventReports(ctx context.Context, from uint64, limit uint64, backwards bool, userID string, roomID string) ([]api.QueryAdminEventReportsResponse, int64, error)
Expand Down
30 changes: 30 additions & 0 deletions roomserver/storage/shared/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -1707,6 +1707,36 @@ func (d *Database) RoomsWithACLs(ctx context.Context) ([]string, error) {
return roomIDs, nil
}

// EmptyRooms returns all rooms that the local server has left.
func (d *Database) EmptyRooms(ctx context.Context) ([]string, error) {
// Get all rooms with m.room.member events, which should be all rooms we know about
eventTypeNID, err := d.GetOrCreateEventTypeNID(ctx, spec.MRoomMember)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to self: this is not needed, as m.room.member is always 5. (Unless someone fiddled with the DB..)

if err != nil {
return nil, err
}

roomNIDs, err := d.EventsTable.SelectRoomsWithEventTypeNID(ctx, nil, eventTypeNID)
if err != nil {
return nil, err
}

// Figure out if we are joined to the rooms
leftRoomsNIDs := make([]types.RoomNID, 0, len(roomNIDs))
for i := 0; i < len(roomNIDs); i++ {
inRoom, err := d.GetLocalServerInRoom(ctx, roomNIDs[i])
if err != nil {
return nil, err
}
if inRoom {
continue
}
// Server is not in the room anymore
leftRoomsNIDs = append(leftRoomsNIDs, roomNIDs[i])
}

return d.RoomsTable.BulkSelectRoomIDs(ctx, nil, leftRoomsNIDs)
}

// ForgetRoom sets a users room to forgotten
func (d *Database) ForgetRoom(ctx context.Context, userID, roomID string, forget bool) error {
roomNIDs, err := d.RoomsTable.BulkSelectRoomNIDs(ctx, nil, []string{roomID})
Expand Down
Loading