etherpad-lite/src/node/db/API.ts

928 lines
27 KiB
TypeScript
Raw Normal View History

2021-01-21 21:06:52 +00:00
'use strict';
/**
* This module provides all API functions
*/
/*
* 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.
*/
2021-01-21 21:06:52 +00:00
const Changeset = require('../../static/js/Changeset');
const ChatMessage = require('../../static/js/ChatMessage');
2021-01-21 21:06:52 +00:00
const CustomError = require('../utils/customError');
2020-11-23 13:24:19 -05:00
const padManager = require('./PadManager');
const padMessageHandler = require('../handler/PadMessageHandler');
const readOnlyManager = require('./ReadOnlyManager');
const groupManager = require('./GroupManager');
const authorManager = require('./AuthorManager');
const sessionManager = require('./SessionManager');
const exportHtml = require('../utils/ExportHtml');
const exportTxt = require('../utils/ExportTxt');
const importHtml = require('../utils/ImportHtml');
const cleanText = require('./Pad').cleanText;
const PadDiff = require('../utils/padDiff');
const {checkValidRev, isInt} = require('../utils/checkValidRev');
/* ********************
* GROUP FUNCTIONS ****
******************** */
exports.listAllGroups = groupManager.listAllGroups;
2011-08-08 16:21:31 +01:00
exports.createGroup = groupManager.createGroup;
exports.createGroupIfNotExistsFor = groupManager.createGroupIfNotExistsFor;
2011-08-09 20:22:39 +01:00
exports.deleteGroup = groupManager.deleteGroup;
2011-08-08 17:35:40 +01:00
exports.listPads = groupManager.listPads;
exports.createGroupPad = groupManager.createGroupPad;
/* ********************
* PADLIST FUNCTION ***
******************** */
exports.listAllPads = padManager.listAllPads;
/* ********************
* AUTHOR FUNCTIONS ***
******************** */
2011-08-09 12:09:04 +01:00
exports.createAuthor = authorManager.createAuthor;
exports.createAuthorIfNotExistsFor = authorManager.createAuthorIfNotExistsFor;
2012-09-04 17:23:33 +02:00
exports.getAuthorName = authorManager.getAuthorName;
exports.listPadsOfAuthor = authorManager.listPadsOfAuthor;
exports.padUsers = padMessageHandler.padUsers;
exports.padUsersCount = padMessageHandler.padUsersCount;
/* ********************
* SESSION FUNCTIONS **
******************** */
2011-08-09 16:45:49 +01:00
exports.createSession = sessionManager.createSession;
2011-08-09 20:22:39 +01:00
exports.deleteSession = sessionManager.deleteSession;
2011-08-09 16:45:49 +01:00
exports.getSessionInfo = sessionManager.getSessionInfo;
exports.listSessionsOfGroup = sessionManager.listSessionsOfGroup;
exports.listSessionsOfAuthor = sessionManager.listSessionsOfAuthor;
/* ***********************
* PAD CONTENT FUNCTIONS *
*********************** */
/**
getAttributePool(padID) returns the attribute pool of a pad
Example returns:
{
"code":0,
"message":"ok",
"data": {
"pool":{
"numToAttrib":{
"0":["author","a.X4m8bBWJBZJnWGSh"],
"1":["author","a.TotfBPzov54ihMdH"],
"2":["author","a.StiblqrzgeNTbK05"],
"3":["bold","true"]
},
"attribToNum":{
"author,a.X4m8bBWJBZJnWGSh":0,
"author,a.TotfBPzov54ihMdH":1,
"author,a.StiblqrzgeNTbK05":2,
"bold,true":3
},
"nextNum":4
}
}
}
*/
2024-02-22 11:36:43 +01:00
exports.getAttributePool = async (padID: string) => {
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
return {pool: pad.pool};
};
/**
getRevisionChangeset (padID, [rev])
get the changeset at a given revision, or last revision if 'rev' is not defined.
Example returns:
{
"code" : 0,
"message" : "ok",
"data" : "Z:1>6b|5+6b$Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at http://etherpad.org\n"
}
*/
2024-02-22 11:36:43 +01:00
exports.getRevisionChangeset = async (padID: string, rev: string) => {
// try to parse the revision number
if (rev !== undefined) {
rev = checkValidRev(rev);
}
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
const head = pad.getHeadRevisionNumber();
// the client asked for a special revision
if (rev !== undefined) {
// check if this is a valid revision
if (rev > head) {
2021-01-21 21:06:52 +00:00
throw new CustomError('rev is higher than the head revision of the pad', 'apierror');
}
// get the changeset for this revision
return await pad.getRevisionChangeset(rev);
}
// the client wants the latest changeset, lets return it to him
return await pad.getRevisionChangeset(head);
2020-11-23 13:24:19 -05:00
};
/**
getText(padID, [rev]) returns the text of a pad
Example returns:
{code: 0, message:"ok", data: {text:"Welcome Text"}}
{code: 1, message:"padID does not exist", data: null}
*/
2024-02-22 11:36:43 +01:00
exports.getText = async (padID: string, rev: string) => {
// try to parse the revision number
if (rev !== undefined) {
rev = checkValidRev(rev);
}
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
const head = pad.getHeadRevisionNumber();
// the client asked for a special revision
if (rev !== undefined) {
// check if this is a valid revision
if (rev > head) {
2021-01-21 21:06:52 +00:00
throw new CustomError('rev is higher than the head revision of the pad', 'apierror');
2011-08-04 17:18:59 +01:00
}
// get the text of this revision
2024-02-22 11:36:43 +01:00
// getInternalRevisionAText() returns an atext object, but we only want the .text inside it.
// Details at https://github.com/ether/etherpad-lite/issues/5073
const {text} = await pad.getInternalRevisionAText(rev);
2020-11-23 13:24:19 -05:00
return {text};
}
// the client wants the latest text, lets return it to him
2020-11-23 13:24:19 -05:00
const text = exportTxt.getTXTFromAtext(pad, pad.atext);
return {text};
};
/**
setText(padID, text, [authorId]) sets the text of a pad
Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
{code: 1, message:"text too long", data: null}
*/
/**
*
* @param {String} padID the id of the pad
* @param {String} text the text of the pad
* @param {String} authorId the id of the author, defaulting to empty string
* @returns {Promise<void>}
*/
2024-02-22 11:36:43 +01:00
exports.setText = async (padID: string, text?: string, authorId: string = ''): Promise<void> => {
// text is required
2020-11-23 13:24:19 -05:00
if (typeof text !== 'string') {
2021-01-21 21:06:52 +00:00
throw new CustomError('text is not a string', 'apierror');
}
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
await pad.setText(text, authorId);
await padMessageHandler.updatePadClients(pad);
2020-11-23 13:24:19 -05:00
};
2015-10-19 12:58:47 -04:00
/**
appendText(padID, text, [authorId]) appends text to a pad
2015-10-19 12:58:47 -04:00
Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
{code: 1, message:"text too long", data: null}
@param {String} padID the id of the pad
@param {String} text the text of the pad
@param {String} authorId the id of the author, defaulting to empty string
*/
2024-02-22 11:36:43 +01:00
exports.appendText = async (padID:string, text?: string, authorId:string = '') => {
// text is required
2020-11-23 13:24:19 -05:00
if (typeof text !== 'string') {
2021-01-21 21:06:52 +00:00
throw new CustomError('text is not a string', 'apierror');
2015-10-19 12:58:47 -04:00
}
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
await pad.appendText(text, authorId);
await padMessageHandler.updatePadClients(pad);
2020-11-23 13:24:19 -05:00
};
2015-10-19 12:58:47 -04:00
/**
getHTML(padID, [rev]) returns the html of a pad
Example returns:
{code: 0, message:"ok", data: {text:"Welcome <strong>Text</strong>"}}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
@param {String} rev the revision number, defaulting to the latest revision
@return {Promise<{html: string}>} the html of the pad
*/
2024-02-22 11:36:43 +01:00
exports.getHTML = async (padID: string, rev: string): Promise<{ html: string; }> => {
if (rev !== undefined) {
rev = checkValidRev(rev);
}
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
// the client asked for a special revision
if (rev !== undefined) {
// check if this is a valid revision
2020-11-23 13:24:19 -05:00
const head = pad.getHeadRevisionNumber();
if (rev > head) {
2021-01-21 21:06:52 +00:00
throw new CustomError('rev is higher than the head revision of the pad', 'apierror');
}
}
// get the html of this revision
let html = await exportHtml.getPadHTML(pad, rev);
// wrap the HTML
2020-11-23 13:24:19 -05:00
html = `<!DOCTYPE HTML><html><body>${html}</body></html>`;
return {html};
};
/**
setHTML(padID, html, [authorId]) sets the text of a pad based on HTML
Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
@param {String} html the html of the pad
@param {String} authorId the id of the author, defaulting to empty string
*/
2024-02-22 11:36:43 +01:00
exports.setHTML = async (padID: string, html:string|object, authorId = '') => {
// html string is required
2020-11-23 13:24:19 -05:00
if (typeof html !== 'string') {
2021-01-21 21:06:52 +00:00
throw new CustomError('html is not a string', 'apierror');
}
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
// add a new changeset with the new html to the pad
try {
await importHtml.setPadHTML(pad, cleanText(html), authorId);
} catch (e) {
2021-01-21 21:06:52 +00:00
throw new CustomError('HTML is malformed', 'apierror');
}
// update the clients on the pad
padMessageHandler.updatePadClients(pad);
};
/* ****************
* CHAT FUNCTIONS *
**************** */
/**
getChatHistory(padId, start, end), returns a part of or the whole chat-history of this pad
Example returns:
2021-01-21 21:06:52 +00:00
{"code":0,"message":"ok","data":{"messages":[
{"text":"foo","authorID":"a.foo","time":1359199533759,"userName":"test"},
{"text":"bar","authorID":"a.foo","time":1359199534622,"userName":"test"}
]}}
{code: 1, message:"start is higher or equal to the current chatHead", data: null}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
@param {Number} start the start point of the chat-history
@param {Number} end the end point of the chat-history
*/
2024-02-22 11:36:43 +01:00
exports.getChatHistory = async (padID: string, start:number, end:number) => {
if (start && end) {
if (start < 0) {
2021-01-21 21:06:52 +00:00
throw new CustomError('start is below zero', 'apierror');
}
if (end < 0) {
2021-01-21 21:06:52 +00:00
throw new CustomError('end is below zero', 'apierror');
}
if (start > end) {
2021-01-21 21:06:52 +00:00
throw new CustomError('start is higher than end', 'apierror');
}
}
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
2020-11-23 13:24:19 -05:00
const chatHead = pad.chatHead;
// fall back to getting the whole chat-history if a parameter is missing
2021-01-21 21:06:52 +00:00
if (!start || !end) {
2013-11-17 16:46:43 +00:00
start = 0;
end = pad.chatHead;
}
if (start > chatHead) {
2021-01-21 21:06:52 +00:00
throw new CustomError('start is higher than the current chatHead', 'apierror');
}
if (end > chatHead) {
2021-01-21 21:06:52 +00:00
throw new CustomError('end is higher than the current chatHead', 'apierror');
}
// the whole message-log and return it to the client
2020-11-23 13:24:19 -05:00
const messages = await pad.getChatMessages(start, end);
2020-11-23 13:24:19 -05:00
return {messages};
};
/**
2021-01-21 21:06:52 +00:00
appendChatMessage(padID, text, authorID, time), creates a chat message for the pad id,
time is a timestamp
Example returns:
2015-08-15 22:41:59 +02:00
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
@param {String} text the text of the chat-message
@param {String} authorID the id of the author
@param {Number} time the timestamp of the chat-message
*/
2024-02-22 11:36:43 +01:00
exports.appendChatMessage = async (padID: string, text: string|object, authorID: string, time: number) => {
// text is required
2020-11-23 13:24:19 -05:00
if (typeof text !== 'string') {
2021-01-21 21:06:52 +00:00
throw new CustomError('text is not a string', 'apierror');
}
// if time is not an integer value set time to current timestamp
2021-01-21 21:06:52 +00:00
if (time === undefined || !isInt(time)) {
time = Date.now();
}
// @TODO - missing getPadSafe() call ?
// save chat message to database and send message to all connected clients
await padMessageHandler.sendChatMessageToPadClients(new ChatMessage(text, authorID, time), padID);
2020-11-23 13:24:19 -05:00
};
/* ***************
* PAD FUNCTIONS *
*************** */
/**
getRevisionsCount(padID) returns the number of revisions of this pad
Example returns:
{code: 0, message:"ok", data: {revisions: 56}}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
*/
2024-02-22 11:36:43 +01:00
exports.getRevisionsCount = async (padID: string) => {
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
return {revisions: pad.getHeadRevisionNumber()};
};
/**
getSavedRevisionsCount(padID) returns the number of saved revisions of this pad
Example returns:
{code: 0, message:"ok", data: {savedRevisions: 42}}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
*/
2024-02-22 11:36:43 +01:00
exports.getSavedRevisionsCount = async (padID: string) => {
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
return {savedRevisions: pad.getSavedRevisionsNumber()};
};
/**
listSavedRevisions(padID) returns the list of saved revisions of this pad
Example returns:
{code: 0, message:"ok", data: {savedRevisions: [2, 42, 1337]}}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
*/
2024-02-22 11:36:43 +01:00
exports.listSavedRevisions = async (padID: string) => {
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
return {savedRevisions: pad.getSavedRevisionsList()};
};
/**
saveRevision(padID) returns the list of saved revisions of this pad
Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
@param {Number} rev the revision number, defaulting to the latest revision
*/
2024-02-22 11:36:43 +01:00
exports.saveRevision = async (padID: string, rev: number) => {
// check if rev is a number
if (rev !== undefined) {
rev = checkValidRev(rev);
}
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
const head = pad.getHeadRevisionNumber();
// the client asked for a special revision
if (rev !== undefined) {
if (rev > head) {
2021-01-21 21:06:52 +00:00
throw new CustomError('rev is higher than the head revision of the pad', 'apierror');
}
} else {
rev = pad.getHeadRevisionNumber();
}
2020-11-23 13:24:19 -05:00
const author = await authorManager.createAuthor('API');
await pad.addSavedRevision(rev, author.authorID, 'Saved through API call');
2020-11-23 13:24:19 -05:00
};
/**
getLastEdited(padID) returns the timestamp of the last revision of the pad
Example returns:
{code: 0, message:"ok", data: {lastEdited: 1340815946602}}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
@return {Promise<{lastEdited: number}>} the timestamp of the last revision of the pad
*/
2024-02-22 11:36:43 +01:00
exports.getLastEdited = async (padID: string): Promise<{ lastEdited: number; }> => {
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
const lastEdited = await pad.getLastEdit();
return {lastEdited};
};
2011-08-04 19:20:14 +01:00
/**
createPad(padName, [text], [authorId]) creates a new pad in this group
2011-08-04 19:20:14 +01:00
Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"pad does already exist", data: null}
2024-02-22 11:36:43 +01:00
@param {String} padID the name of the new pad
@param {String} text the initial text of the pad
@param {String} authorId the id of the author, defaulting to empty string
2011-08-04 19:20:14 +01:00
*/
2024-02-22 11:36:43 +01:00
exports.createPad = async (padID: string, text: string, authorId = '') => {
if (padID) {
// ensure there is no $ in the padID
2020-11-23 13:24:19 -05:00
if (padID.indexOf('$') !== -1) {
2021-01-21 21:06:52 +00:00
throw new CustomError("createPad can't create group pads", 'apierror');
}
// check for url special characters
if (padID.match(/(\/|\?|&|#)/)) {
2021-01-21 21:06:52 +00:00
throw new CustomError('malformed padID: Remove special characters', 'apierror');
}
2011-08-04 19:20:14 +01:00
}
// create pad
await getPadSafe(padID, false, text, authorId);
2020-11-23 13:24:19 -05:00
};
2011-08-04 19:20:14 +01:00
/**
deletePad(padID) deletes a pad
Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
*/
2024-02-22 11:36:43 +01:00
exports.deletePad = async (padID: string) => {
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
await pad.remove();
2020-11-23 13:24:19 -05:00
};
2014-11-08 01:39:27 +01:00
/**
restoreRevision(padID, rev, [authorId]) Restores revision from past as new changeset
2014-11-08 01:39:27 +01:00
Example returns:
2014-11-08 01:39:27 +01:00
{code:0, message:"ok", data:null}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
@param {Number} rev the revision number, defaulting to the latest revision
@param {String} authorId the id of the author, defaulting to empty string
2014-11-08 01:39:27 +01:00
*/
2024-02-22 11:36:43 +01:00
exports.restoreRevision = async (padID: string, rev: number, authorId = '') => {
// check if rev is a number
if (rev === undefined) {
2021-01-21 21:06:52 +00:00
throw new CustomError('rev is not defined', 'apierror');
2014-11-12 19:53:56 +01:00
}
rev = checkValidRev(rev);
2014-11-12 19:53:56 +01:00
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
2014-11-12 19:53:56 +01:00
// check if this is a valid revision
if (rev > pad.getHeadRevisionNumber()) {
2021-01-21 21:06:52 +00:00
throw new CustomError('rev is higher than the head revision of the pad', 'apierror');
2014-11-12 19:53:56 +01:00
}
2020-11-23 13:24:19 -05:00
const atext = await pad.getInternalRevisionAText(rev);
2014-11-12 19:53:56 +01:00
2020-11-23 13:24:19 -05:00
const oldText = pad.text();
atext.text += '\n';
2014-11-12 19:53:56 +01:00
2024-02-22 11:36:43 +01:00
const eachAttribRun = (attribs: string[], func:Function) => {
2020-11-23 13:24:19 -05:00
let textIndex = 0;
const newTextStart = 0;
const newTextEnd = atext.text.length;
for (const op of Changeset.deserializeOps(attribs)) {
2020-11-23 13:24:19 -05:00
const nextIndex = textIndex + op.chars;
if (!(nextIndex <= newTextStart || textIndex >= newTextEnd)) {
func(Math.max(newTextStart, textIndex), Math.min(newTextEnd, nextIndex), op.attribs);
2014-11-12 19:53:56 +01:00
}
textIndex = nextIndex;
}
2021-01-21 21:06:52 +00:00
};
2014-11-12 19:53:56 +01:00
// create a new changeset with a helper builder object
2020-11-23 13:24:19 -05:00
const builder = Changeset.builder(oldText.length);
2014-11-12 19:53:56 +01:00
// assemble each line into the builder
2024-02-22 11:36:43 +01:00
eachAttribRun(atext.attribs, (start: number, end: number, attribs:string[]) => {
builder.insert(atext.text.substring(start, end), attribs);
});
2014-11-12 19:53:56 +01:00
2020-11-23 13:24:19 -05:00
const lastNewlinePos = oldText.lastIndexOf('\n');
if (lastNewlinePos < 0) {
builder.remove(oldText.length - 1, 0);
} else {
builder.remove(lastNewlinePos, oldText.match(/\n/g).length - 1);
builder.remove(oldText.length - lastNewlinePos - 1, 0);
}
2014-11-12 19:53:56 +01:00
2020-11-23 13:24:19 -05:00
const changeset = builder.toString();
await pad.appendRevision(changeset, authorId);
await padMessageHandler.updatePadClients(pad);
2020-11-23 13:24:19 -05:00
};
2013-11-17 16:46:43 +00:00
/**
copyPad(sourceID, destinationID[, force=false]) copies a pad. If force is true,
the destination will be overwritten if it exists.
2013-11-17 16:46:43 +00:00
Example returns:
{code: 0, message:"ok", data: {padID: destinationID}}
2013-11-17 16:46:43 +00:00
{code: 1, message:"padID does not exist", data: null}
@param {String} sourceID the id of the source pad
@param {String} destinationID the id of the destination pad
@param {Boolean} force whether to overwrite the destination pad if it exists
2013-11-17 16:46:43 +00:00
*/
2024-02-22 11:36:43 +01:00
exports.copyPad = async (sourceID: string, destinationID: string, force: boolean) => {
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(sourceID, true);
await pad.copy(destinationID, force);
2020-11-23 13:24:19 -05:00
};
2013-11-17 16:46:43 +00:00
/**
copyPadWithoutHistory(sourceID, destinationID[, force=false], [authorId]) copies a pad. If force is
true, the destination will be overwritten if it exists.
Example returns:
{code: 0, message:"ok", data: {padID: destinationID}}
{code: 1, message:"padID does not exist", data: null}
@param {String} sourceID the id of the source pad
@param {String} destinationID the id of the destination pad
@param {Boolean} force whether to overwrite the destination pad if it exists
@param {String} authorId the id of the author, defaulting to empty string
*/
2024-02-22 11:36:43 +01:00
exports.copyPadWithoutHistory = async (sourceID: string, destinationID: string, force:boolean, authorId = '') => {
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(sourceID, true);
await pad.copyPadWithoutHistory(destinationID, force, authorId);
2020-11-23 13:24:19 -05:00
};
2013-11-17 16:46:43 +00:00
/**
movePad(sourceID, destinationID[, force=false]) moves a pad. If force is true,
the destination will be overwritten if it exists.
2013-11-17 16:46:43 +00:00
Example returns:
{code: 0, message:"ok", data: {padID: destinationID}}
2013-11-17 16:46:43 +00:00
{code: 1, message:"padID does not exist", data: null}
@param {String} sourceID the id of the source pad
@param {String} destinationID the id of the destination pad
@param {Boolean} force whether to overwrite the destination pad if it exists
2013-11-17 16:46:43 +00:00
*/
2024-02-22 11:36:43 +01:00
exports.movePad = async (sourceID: string, destinationID: string, force:boolean) => {
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(sourceID, true);
await pad.copy(destinationID, force);
await pad.remove();
2020-11-23 13:24:19 -05:00
};
/**
getReadOnlyLink(padID) returns the read only link of a pad
Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
*/
2024-02-22 11:36:43 +01:00
exports.getReadOnlyID = async (padID: string) => {
// we don't need the pad object, but this function does all the security stuff for us
await getPadSafe(padID, true);
// get the readonlyId
2020-11-23 13:24:19 -05:00
const readOnlyID = await readOnlyManager.getReadOnlyId(padID);
2020-11-23 13:24:19 -05:00
return {readOnlyID};
};
/**
getPadID(roID) returns the padID of a pad based on the readonlyID(roID)
Example returns:
{code: 0, message:"ok", data: {padID: padID}}
{code: 1, message:"padID does not exist", data: null}
@param {String} roID the readonly id of the pad
*/
2024-02-22 11:36:43 +01:00
exports.getPadID = async (roID: string) => {
// get the PadId
2020-11-23 13:24:19 -05:00
const padID = await readOnlyManager.getPadId(roID);
2021-01-21 21:06:52 +00:00
if (padID == null) {
throw new CustomError('padID does not exist', 'apierror');
}
2020-11-23 13:24:19 -05:00
return {padID};
};
/**
setPublicStatus(padID, publicStatus) sets a boolean for the public status of a pad
Example returns:
{code: 0, message:"ok", data: null}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
@param {Boolean} publicStatus the public status of the pad
*/
2024-02-22 11:36:43 +01:00
exports.setPublicStatus = async (padID: string, publicStatus: boolean|string) => {
// ensure this is a group pad
2020-11-23 13:24:19 -05:00
checkGroupPad(padID, 'publicStatus');
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
// convert string to boolean
2020-11-23 13:24:19 -05:00
if (typeof publicStatus === 'string') {
publicStatus = (publicStatus.toLowerCase() === 'true');
}
await pad.setPublicStatus(publicStatus);
2020-11-23 13:24:19 -05:00
};
/**
getPublicStatus(padID) return true of false
Example returns:
{code: 0, message:"ok", data: {publicStatus: true}}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
*/
2024-02-22 11:36:43 +01:00
exports.getPublicStatus = async (padID: string) => {
// ensure this is a group pad
2020-11-23 13:24:19 -05:00
checkGroupPad(padID, 'publicStatus');
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
return {publicStatus: pad.getPublicStatus()};
};
/**
listAuthorsOfPad(padID) returns an array of authors who contributed to this pad
Example returns:
{code: 0, message:"ok", data: {authorIDs : ["a.s8oes9dhwrvt0zif", "a.akf8finncvomlqva"]}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
*/
2024-02-22 11:36:43 +01:00
exports.listAuthorsOfPad = async (padID: string) => {
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
const authorIDs = pad.getAllAuthors();
return {authorIDs};
};
/**
sendClientsMessage(padID, msg) sends a message to all clients connected to the
pad, possibly for the purpose of signalling a plugin.
Note, this will only accept strings from the HTTP API, so sending bogus changes
or chat messages will probably not be possible.
The resulting message will be structured like so:
{
type: 'COLLABROOM',
data: {
type: <msg>,
time: <time the message was sent>
}
}
Example returns:
{code: 0, message:"ok"}
{code: 1, message:"padID does not exist"}
@param {String} padID the id of the pad
@param {String} msg the message to send
*/
2024-02-22 11:36:43 +01:00
exports.sendClientsMessage = async (padID: string, msg: string) => {
await getPadSafe(padID, true); // Throw if the padID is invalid or if the pad does not exist.
padMessageHandler.handleCustomMessage(padID, msg);
2020-11-23 13:24:19 -05:00
};
/**
checkToken() returns ok when the current api token is valid
Example returns:
{"code":0,"message":"ok","data":null}
{"code":4,"message":"no or wrong API Key","data":null}
*/
2021-01-21 21:06:52 +00:00
exports.checkToken = async () => {
2020-11-23 13:24:19 -05:00
};
/**
getChatHead(padID) returns the chatHead (last number of the last chat-message) of the pad
Example returns:
{code: 0, message:"ok", data: {chatHead: 42}}
{code: 1, message:"padID does not exist", data: null}
@param {String} padID the id of the pad
@return {Promise<{chatHead: number}>} the chatHead of the pad
*/
2024-02-22 11:36:43 +01:00
exports.getChatHead = async (padID:string): Promise<{ chatHead: number; }> => {
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
return {chatHead: pad.chatHead};
};
/**
2013-01-28 16:52:23 +00:00
createDiffHTML(padID, startRev, endRev) returns an object of diffs from 2 points in a pad
Example returns:
2021-01-21 21:06:52 +00:00
{
"code": 0,
"message": "ok",
"data": {
"html": "...",
"authors": [
"a.HKIv23mEbachFYfH",
""
]
}
}
2013-01-27 17:51:40 +00:00
{"code":4,"message":"no or wrong API Key","data":null}
@param {String} padID the id of the pad
@param {Number} startRev the start revision number
@param {Number} endRev the end revision number
*/
2024-02-22 11:36:43 +01:00
exports.createDiffHTML = async (padID: string, startRev: number, endRev: number) => {
// check if startRev is a number
if (startRev !== undefined) {
startRev = checkValidRev(startRev);
}
// check if endRev is a number
if (endRev !== undefined) {
endRev = checkValidRev(endRev);
}
// get the pad
2020-11-23 13:24:19 -05:00
const pad = await getPadSafe(padID, true);
const headRev = pad.getHeadRevisionNumber();
if (startRev > headRev) startRev = headRev;
if (endRev > headRev) endRev = headRev;
2021-01-21 21:06:52 +00:00
let padDiff;
try {
2021-01-21 21:06:52 +00:00
padDiff = new PadDiff(pad, startRev, endRev);
2024-02-22 11:36:43 +01:00
} catch (e:any) {
2020-11-23 13:24:19 -05:00
throw {stop: e.message};
}
2020-11-23 13:24:19 -05:00
const html = await padDiff.getHtml();
const authors = await padDiff.getAuthors();
2020-11-23 13:24:19 -05:00
return {html, authors};
};
/* ********************
** GLOBAL FUNCTIONS **
******************** */
2020-04-01 10:57:43 +02:00
/**
getStats() returns an json object with some instance stats
Example returns:
{"code":0,"message":"ok","data":{"totalPads":3,"totalSessions": 2,"totalActivePads": 1}}
{"code":4,"message":"no or wrong API Key","data":null}
*/
2021-01-21 21:06:52 +00:00
exports.getStats = async () => {
const sessionInfos = padMessageHandler.sessioninfos;
const sessionKeys = Object.keys(sessionInfos);
2024-02-22 11:36:43 +01:00
// @ts-ignore
const activePads = new Set(Object.entries(sessionInfos).map((k) => k[1].padId));
2020-11-23 13:24:19 -05:00
const {padIDs} = await padManager.listAllPads();
2020-04-01 10:57:43 +02:00
return {
totalPads: padIDs.length,
totalSessions: sessionKeys.length,
2020-04-01 10:57:43 +02:00
totalActivePads: activePads.size,
2020-11-23 13:24:19 -05:00
};
};
2020-04-01 10:57:43 +02:00
/* ****************************
** INTERNAL HELPER FUNCTIONS *
**************************** */
// gets a pad safe
2024-02-22 11:36:43 +01:00
const getPadSafe = async (padID: string|object, shouldExist: boolean, text?:string, authorId:string = '') => {
// check if padID is a string
2020-11-23 13:24:19 -05:00
if (typeof padID !== 'string') {
2021-01-21 21:06:52 +00:00
throw new CustomError('padID is not a string', 'apierror');
2011-08-04 17:18:59 +01:00
}
// check if the padID maches the requirements
if (!padManager.isValidPadId(padID)) {
2021-01-21 21:06:52 +00:00
throw new CustomError('padID did not match requirements', 'apierror');
2011-08-04 17:18:59 +01:00
}
// check if the pad exists
2020-11-23 13:24:19 -05:00
const exists = await padManager.doesPadExists(padID);
if (!exists && shouldExist) {
// does not exist, but should
2021-01-21 21:06:52 +00:00
throw new CustomError('padID does not exist', 'apierror');
}
if (exists && !shouldExist) {
// does exist, but shouldn't
2021-01-21 21:06:52 +00:00
throw new CustomError('padID does already exist', 'apierror');
}
// pad exists, let's get it
return padManager.getPad(padID, text, authorId);
};
// checks if a padID is part of a group
2024-02-22 11:36:43 +01:00
const checkGroupPad = (padID: string, field: string) => {
// ensure this is a group pad
2020-11-23 13:24:19 -05:00
if (padID && padID.indexOf('$') === -1) {
2021-01-21 21:06:52 +00:00
throw new CustomError(
`You can only get/set the ${field} of pads that belong to a group`, 'apierror');
}
2021-01-21 21:06:52 +00:00
};