'use strict'; /** * The Group Manager provides functions to manage groups in the database */ /* * 2011 Peter 'Pita' Martischka (Primary Technology Ltd) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS-IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import CustomError from '../utils/customError'; const randomString = require('../../static/js/pad_utils').randomString; import {get, getSub, remove, set, setSub} from './DB'; import {doesPadExist, getPad} from './PadManager'; import {deleteSession} from './SessionManager'; /** * Lists all groups * @return {Promise<{groupIDs: string[]}>} The ids of all groups */ export const listAllGroups = async (): Promise<{ groupIDs: string[]; }> => { let groups = await get('groups'); groups = groups || {}; const groupIDs = Object.keys(groups); return {groupIDs}; }; /** * Deletes a group and all associated pads * @param {String} groupID The id of the group * @return {Promise} Resolves when the group is deleted */ export const deleteGroup = async (groupID: string): Promise => { const group = await get(`group:${groupID}`); // ensure group exists if (group == null) { // group does not exist throw new CustomError('groupID does not exist', 'apierror'); } // iterate through all pads of this group and delete them (in parallel) await Promise.all(Object.keys(group.pads).map(async (padId) => { const pad = await getPad(padId); await pad.remove(); })); // Delete associated sessions in parallel. This should be done before deleting the group2sessions // record because deleting a session updates the group2sessions record. const {sessionIDs = {}} = await get(`group2sessions:${groupID}`) || {}; await Promise.all(Object.keys(sessionIDs).map(async (sessionId) => { await deleteSession(sessionId); })); await Promise.all([ remove(`group2sessions:${groupID}`), // UeberDB's setSub() method atomically reads the record, updates the appropriate property, and // writes the result. Setting a property to `undefined` deletes that property (JSON.stringify() // ignores such properties). setSub('groups', [groupID], undefined), ...Object.keys(group.mappings || {}).map(async (m) => await remove(`mapper2group:${m}`)), ]); // Remove the group record after updating the `groups` record so that the state is consistent. await remove(`group:${groupID}`); }; /** * Checks if a group exists * @param {String} groupID the id of the group to delete * @return {Promise} Resolves to true if the group exists */ export const doesGroupExist = async (groupID: string) => { // try to get the group entry const group = await get(`group:${groupID}`); return (group != null); }; /** * Creates a new group * @return {Promise<{groupID: string}>} the id of the new group */ export const createGroup = async (): Promise<{ groupID: string; }> => { const groupID = `g.${randomString(16)}`; await set(`group:${groupID}`, {pads: {}, mappings: {}}); // 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 appropriate property, and writes the result. await setSub('groups', [groupID], 1); return {groupID}; }; /** * Creates a new group if it does not exist already and returns the group ID * @param groupMapper the mapper of the group * @return {Promise<{groupID: string}|{groupID: *}>} a promise that resolves to the group ID */ export const createGroupIfNotExistsFor = async (groupMapper: string|object) => { if (typeof groupMapper !== 'string') { throw new CustomError('groupMapper is not a string', 'apierror'); } const groupID = await get(`mapper2group:${groupMapper}`) as string; if (groupID && await doesGroupExist(groupID)) return {groupID}; const result = await createGroup(); await Promise.all([ 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.) setSub(`group:${result.groupID}`, ['mappings', groupMapper], 1), ]); return result; }; /** * Creates a group pad * @param {String} groupID The id of the group * @param {String} padName The name of the pad * @param {String} text The text of the pad * @param {String} authorId The id of the author * @return {Promise<{padID: string}>} a promise that resolves to the id of the new pad */ export const createGroupPad = async (groupID: string, padName: string, text: string, authorId: string = ''): Promise<{ padID: string; }> => { // create the padID const padID = `${groupID}$${padName}`; // ensure group exists const groupExists = await doesGroupExist(groupID); if (!groupExists) { throw new CustomError('groupID does not exist', 'apierror'); } // ensure pad doesn't exist already const padExists = await doesPadExist(padID); if (padExists) { // pad exists already throw new CustomError('padName does already exist', 'apierror'); } // create the pad await getPad(padID, text, authorId); // create an entry in the group for this pad await setSub(`group:${groupID}`, ['pads', padID], 1); return {padID}; }; /** * Lists all pads of a group * @param {String} groupID The id of the group * @return {Promise<{padIDs: string[]}>} a promise that resolves to the ids of all pads of the group */ export const listPads = async (groupID: string): Promise<{ padIDs: string[]; }> => { const exists = await doesGroupExist(groupID); // ensure the group exists if (!exists) { throw new CustomError('groupID does not exist', 'apierror'); } // group exists, let's get the pads const result = await getSub(`group:${groupID}`, ['pads']); const padIDs = Object.keys(result); return {padIDs}; };