Explain the query is trying to do
[gitter.git] / scripts / utils / clean-up-orphaned-matrix-rooms.js
blob882d416de60501314f24c363a254bdf400823c31
1 #!/usr/bin/env node
2 //
3 // Usage:
4 //  - Linux/macOS: matrix__bridge__applicationServicePort=9001 node ./scripts/utils/clean-up-orphaned-matrix-rooms.js
5 //  - Windows: set matrix__bridge__applicationServicePort=9001&&node ./scripts/utils/clean-up-orphaned-matrix-rooms.js
6 //
7 'use strict';
9 const assert = require('assert');
10 const shutdown = require('shutdown');
11 const persistence = require('gitter-web-persistence');
12 const { iterableFromMongooseCursor } = require('gitter-web-persistence-utils/lib/mongoose-utils');
13 const mongoReadPrefs = require('gitter-web-persistence-utils/lib/mongo-read-prefs');
15 const installBridge = require('gitter-web-matrix-bridge');
16 const matrixBridge = require('gitter-web-matrix-bridge/lib/matrix-bridge');
17 const MatrixUtils = require('gitter-web-matrix-bridge/lib/matrix-utils');
19 require('../../server/event-listeners').install();
21 const matrixUtils = new MatrixUtils(matrixBridge);
23 const opts = require('yargs')
24   .option('delay', {
25     alias: 'd',
26     type: 'number',
27     required: true,
28     default: 2000,
29     description:
30       'Delay timeout(in milliseconds) between rooms to shutdown to not overwhelm the homeserver'
31   })
32   .option('dry-run', {
33     description: 'Dry-run. Do not execute, just print',
34     type: 'boolean',
35     default: false
36   })
37   .help('help')
38   .alias('help', 'h').argv;
40 let numberOfRoomsShutdown = 0;
41 let numberOfRoomsIgnored = 0;
42 const failedBridgedRoomShutdowns = [];
44 async function shutdownBridgedMatrixRoom(bridgedRoomEntry) {
45   try {
46     assert(bridgedRoomEntry.matrixRoomId);
47     assert(bridgedRoomEntry.troupeId);
48     console.log(
49       `${opts.dryRun ? 'Dry-run: ' : ''}Shutting down matrixRoomId=${
50         bridgedRoomEntry.matrixRoomId
51       }, gitterRoomId=${bridgedRoomEntry.troupeId}`
52     );
54     if (!opts.dryRun) {
55       await matrixUtils.shutdownMatrixRoom(bridgedRoomEntry.matrixRoomId);
56     }
57     numberOfRoomsShutdown += 1;
58   } catch (err) {
59     // This error occurs for rooms which don't exist or we can't get access to
60     // the room anyway. We don't need to worry about these cases. e.g.
61     // "M_FORBIDDEN: User @gitter-badger:my.matrix.host not in room
62     // !1605079432013:localhost, and room previews are disabled"
63     if (err.errcode === 'M_FORBIDDEN') {
64       console.log(
65         `${bridgedRoomEntry.matrixRoomId} is already deleted or we don't have access anymore to delete it so we can just ignore it -> ${err.errcode}: ${err.error}`
66       );
67       numberOfRoomsIgnored += 1;
68     } else {
69       throw err;
70     }
71   }
74 async function shutdownOrphanedRooms() {
75   // Find bridged Matrix rooms where the Gitter room (troupe) no longer exists
76   const cursor = await persistence.MatrixBridgedRoom.aggregate([
77     {
78       // Lookup troupes._id === matricesbridgedroom.troupeId
79       $lookup: {
80         from: 'troupes',
81         // Field from MatrixBridgedRoom
82         localField: 'troupeId',
83         // Field from Troupe
84         foreignField: '_id',
85         as: 'troupe'
86       }
87     },
88     {
89       $match: {
90         'troupe._id': {
91           $exists: false
92         }
93       }
94     }
95   ])
96     .read(mongoReadPrefs.secondaryPreferred)
97     .cursor({ batchSize: 1000, async: true })
98     .exec();
100   const iterable = iterableFromMongooseCursor(cursor);
102   for await (let bridgedRoomEntry of iterable) {
103     try {
104       await shutdownBridgedMatrixRoom(bridgedRoomEntry);
105     } catch (err) {
106       console.error(
107         `Failed to shutdown matrixRoomId=${bridgedRoomEntry.matrixRoomId}, gitterRoomId=${bridgedRoomEntry.troupeId}`,
108         err,
109         err.stack
110       );
111       failedBridgedRoomShutdowns.push(bridgedRoomEntry);
112     }
114     // Put a delay between each time we process and shutdown a bridged room
115     // to avoid overwhelming and hitting the rate-limits on the Matrix homeserver
116     if (opts.delay > 0) {
117       await new Promise(resolve => {
118         setTimeout(resolve, opts.delay);
119       });
120     }
121   }
124 async function run() {
125   try {
126     if (opts.dryRun) {
127       console.log('Dry-run, nothing will actually be deleted =================');
128       console.log('===========================================================');
129     }
131     console.log('Setting up Matrix bridge');
132     await installBridge();
134     console.log('Starting to shutdown orphaned bridged rooms');
135     await shutdownOrphanedRooms();
136     console.log(
137       `${numberOfRoomsShutdown} orphaned bridged shutdown! Ignored ${numberOfRoomsIgnored} orphaned rooms which are already deleted.`
138     );
140     if (failedBridgedRoomShutdowns.length) {
141       console.warn(
142         `But some rooms failed to shutdown (${failedBridgedRoomShutdowns.length})`,
143         failedBridgedRoomShutdowns
144       );
145     }
147     // wait 5 seconds to allow for asynchronous `event-listeners` to finish
148     // This isn't clean but works
149     // https://github.com/troupe/gitter-webapp/issues/580#issuecomment-147445395
150     // https://gitlab.com/gitterHQ/webapp/merge_requests/1605#note_222861592
151     console.log(`Waiting 5 seconds to allow for the asynchronous \`event-listeners\` to finish...`);
152     await new Promise(resolve => setTimeout(resolve, 5000));
153   } catch (err) {
154     console.error(err, err.stack);
155   }
156   shutdown.shutdownGracefully();
159 run();