GroupManager: Clean up any mappings when deleting a group
parent
5b37a56197
commit
777d045246
|
@ -3,6 +3,8 @@
|
||||||
### Notable enhancements and fixes
|
### Notable enhancements and fixes
|
||||||
|
|
||||||
* Fixed a potential attribute pool corruption bug with `copyPadWithoutHistory`.
|
* Fixed a potential attribute pool corruption bug with `copyPadWithoutHistory`.
|
||||||
|
* Mappings created by the `createGroupIfNotExistsFor` HTTP API are now removed
|
||||||
|
from the database when the group is deleted.
|
||||||
|
|
||||||
#### For plugin authors
|
#### For plugin authors
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,7 @@ exports.deleteGroup = async (groupID) => {
|
||||||
// writes the result. Setting a property to `undefined` deletes that property (JSON.stringify()
|
// writes the result. Setting a property to `undefined` deletes that property (JSON.stringify()
|
||||||
// ignores such properties).
|
// ignores such properties).
|
||||||
db.setSub('groups', [groupID], undefined),
|
db.setSub('groups', [groupID], undefined),
|
||||||
|
...Object.keys(group.mappings || {}).map(async (m) => await db.remove(`mapper2group:${m}`)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Remove the group record after updating the `groups` record so that the state is consistent.
|
// Remove the group record after updating the `groups` record so that the state is consistent.
|
||||||
|
@ -76,7 +77,7 @@ exports.doesGroupExist = async (groupID) => {
|
||||||
|
|
||||||
exports.createGroup = async () => {
|
exports.createGroup = async () => {
|
||||||
const groupID = `g.${randomString(16)}`;
|
const groupID = `g.${randomString(16)}`;
|
||||||
await db.set(`group:${groupID}`, {pads: {}});
|
await db.set(`group:${groupID}`, {pads: {}, mappings: {}});
|
||||||
// Add the group to the `groups` record after the group's individual record is created so that
|
// Add the group to the `groups` record after the group's individual record is created so that
|
||||||
// the state is consistent. Note: UeberDB's setSub() method atomically reads the record, updates
|
// the state is consistent. Note: UeberDB's setSub() method atomically reads the record, updates
|
||||||
// the appropriate property, and writes the result.
|
// the appropriate property, and writes the result.
|
||||||
|
@ -85,27 +86,20 @@ exports.createGroup = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.createGroupIfNotExistsFor = async (groupMapper) => {
|
exports.createGroupIfNotExistsFor = async (groupMapper) => {
|
||||||
// ensure mapper is optional
|
|
||||||
if (typeof groupMapper !== 'string') {
|
if (typeof groupMapper !== 'string') {
|
||||||
throw new CustomError('groupMapper is not a string', 'apierror');
|
throw new CustomError('groupMapper is not a string', 'apierror');
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to get a group for this mapper
|
|
||||||
const groupID = await db.get(`mapper2group:${groupMapper}`);
|
const groupID = await db.get(`mapper2group:${groupMapper}`);
|
||||||
|
if (groupID && await exports.doesGroupExist(groupID)) return {groupID};
|
||||||
if (groupID) {
|
|
||||||
// there is a group for this mapper
|
|
||||||
const exists = await exports.doesGroupExist(groupID);
|
|
||||||
|
|
||||||
if (exists) return {groupID};
|
|
||||||
}
|
|
||||||
|
|
||||||
// hah, the returned group doesn't exist, let's create one
|
|
||||||
const result = await exports.createGroup();
|
const result = await exports.createGroup();
|
||||||
|
await Promise.all([
|
||||||
// create the mapper entry for this group
|
db.set(`mapper2group:${groupMapper}`, result.groupID),
|
||||||
await db.set(`mapper2group:${groupMapper}`, result.groupID);
|
// Remember the mapping in the group record so that it can be cleaned up when the group is
|
||||||
|
// deleted. Although the core Etherpad API does not support multiple mappings for the same
|
||||||
|
// group, the database record does support multiple mappings in case a plugin decides to extend
|
||||||
|
// the core Etherpad functionality. (It's also easy to implement it this way.)
|
||||||
|
db.setSub(`group:${result.groupID}`, ['mappings', groupMapper], 1),
|
||||||
|
]);
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
const assert = require('assert').strict;
|
const assert = require('assert').strict;
|
||||||
const common = require('../../common');
|
const common = require('../../common');
|
||||||
|
const db = require('../../../../node/db/DB');
|
||||||
|
|
||||||
let agent;
|
let agent;
|
||||||
const apiKey = common.apiKey;
|
const apiKey = common.apiKey;
|
||||||
|
@ -89,13 +90,33 @@ describe(__filename, function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('createGroupIfNotExistsFor', async function () {
|
it('createGroupIfNotExistsFor', async function () {
|
||||||
await agent.get(`${endPoint('createGroupIfNotExistsFor')}&groupMapper=management`)
|
const mapper = makeid();
|
||||||
|
let groupId;
|
||||||
|
await agent.get(`${endPoint('createGroupIfNotExistsFor')}&groupMapper=${mapper}`)
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.expect('Content-Type', /json/)
|
.expect('Content-Type', /json/)
|
||||||
.expect((res) => {
|
.expect((res) => {
|
||||||
assert.equal(res.body.code, 0);
|
assert.equal(res.body.code, 0);
|
||||||
assert(res.body.data.groupID);
|
groupId = res.body.data.groupID;
|
||||||
|
assert(groupId);
|
||||||
});
|
});
|
||||||
|
// Passing the same mapper should return the same group ID.
|
||||||
|
await agent.get(`${endPoint('createGroupIfNotExistsFor')}&groupMapper=${mapper}`)
|
||||||
|
.expect(200)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect((res) => {
|
||||||
|
assert.equal(res.body.code, 0);
|
||||||
|
assert.equal(res.body.data.groupID, groupId);
|
||||||
|
});
|
||||||
|
// Deleting the group should clean up the mapping.
|
||||||
|
assert.equal(await db.get(`mapper2group:${mapper}`), groupId);
|
||||||
|
await agent.get(`${endPoint('deleteGroup')}&groupID=${groupId}`)
|
||||||
|
.expect(200)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect((res) => {
|
||||||
|
assert.equal(res.body.code, 0);
|
||||||
|
});
|
||||||
|
assert(await db.get(`mapper2group:${mapper}`) == null);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Test coverage for https://github.com/ether/etherpad-lite/issues/4227
|
// Test coverage for https://github.com/ether/etherpad-lite/issues/4227
|
||||||
|
|
Loading…
Reference in New Issue