mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-04-20 15:36:16 -04:00
API: Add optional authorId
param to mutation functions
This commit is contained in:
parent
50fafe608b
commit
aa286b7dbd
6 changed files with 93 additions and 42 deletions
|
@ -26,6 +26,9 @@
|
|||
database when the group is deleted.
|
||||
* Fixed race conditions in the `setText`, `appendText`, and `restoreRevision`
|
||||
functions.
|
||||
* Added an optional `authorId` parameter to `appendText`,
|
||||
`copyPadWithoutHistory`, `createGroupPad`, `createPad`, `restoreRevision`,
|
||||
`setHTML`, and `setText`, and bumped the latest API version to 1.3.0.
|
||||
* Fixed a crash if the database is busy enough to cause a query timeout.
|
||||
* New `/health` endpoint for getting information about Etherpad's health (see
|
||||
[draft-inadarei-api-health-check-06](https://www.ietf.org/archive/id/draft-inadarei-api-health-check-06.html)).
|
||||
|
|
|
@ -173,8 +173,9 @@ returns all pads of this group
|
|||
* `{code: 0, message:"ok", data: {padIDs : ["g.s8oes9dhwrvt0zif$test", "g.s8oes9dhwrvt0zif$test2"]}`
|
||||
* `{code: 1, message:"groupID does not exist", data: null}`
|
||||
|
||||
#### createGroupPad(groupID, padName [, text])
|
||||
#### createGroupPad(groupID, padName, [text], [authorId])
|
||||
* API >= 1
|
||||
* `authorId` in API >= 1.3.0
|
||||
|
||||
creates a new pad in this group
|
||||
|
||||
|
@ -293,8 +294,9 @@ returns the text of a pad
|
|||
* `{code: 0, message:"ok", data: {text:"Welcome Text"}}`
|
||||
* `{code: 1, message:"padID does not exist", data: null}`
|
||||
|
||||
#### setText(padID, text)
|
||||
#### setText(padID, text, [authorId])
|
||||
* API >= 1
|
||||
* `authorId` in API >= 1.3.0
|
||||
|
||||
Sets the text of a pad.
|
||||
|
||||
|
@ -305,8 +307,9 @@ If your text is long (>8 KB), please invoke via POST and include `text` paramete
|
|||
* `{code: 1, message:"padID does not exist", data: null}`
|
||||
* `{code: 1, message:"text too long", data: null}`
|
||||
|
||||
#### appendText(padID, text)
|
||||
#### appendText(padID, text, [authorId])
|
||||
* API >= 1.2.13
|
||||
* `authorId` in API >= 1.3.0
|
||||
|
||||
Appends text to a pad.
|
||||
|
||||
|
@ -326,8 +329,9 @@ returns the text of a pad formatted as HTML
|
|||
* `{code: 0, message:"ok", data: {html:"Welcome Text<br>More Text"}}`
|
||||
* `{code: 1, message:"padID does not exist", data: null}`
|
||||
|
||||
#### setHTML(padID, html)
|
||||
#### setHTML(padID, html, [authorId])
|
||||
* API >= 1
|
||||
* `authorId` in API >= 1.3.0
|
||||
|
||||
sets the text of a pad based on HTML, HTML must be well-formed. Malformed HTML will send a warning to the API log.
|
||||
|
||||
|
@ -387,8 +391,9 @@ returns an object of diffs from 2 points in a pad
|
|||
* `{"code":0,"message":"ok","data":{"html":"<style>\n.authora_HKIv23mEbachFYfH {background-color: #a979d9}\n.authora_n4gEeMLsv1GivNeh {background-color: #a9b5d9}\n.removed {text-decoration: line-through; -ms-filter:'progid:DXImageTransform.Microsoft.Alpha(Opacity=80)'; filter: alpha(opacity=80); opacity: 0.8; }\n</style>Welcome to Etherpad!<br><br>This 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!<br><br>Get involved with Etherpad at <a href=\"http://etherpad.org\">http://etherpad.org</a><br><span class=\"authora_HKIv23mEbachFYfH\">aw</span><br><br>","authors":["a.HKIv23mEbachFYfH",""]}}`
|
||||
* `{"code":4,"message":"no or wrong API Key","data":null}`
|
||||
|
||||
#### restoreRevision(padId, rev)
|
||||
#### restoreRevision(padId, rev, [authorId])
|
||||
* API >= 1.2.11
|
||||
* `authorId` in API >= 1.3.0
|
||||
|
||||
Restores revision from past as new changeset
|
||||
|
||||
|
@ -437,8 +442,9 @@ creates a chat message, saves it to the database and sends it to all connected c
|
|||
### Pad
|
||||
Group pads are normal pads, but with the name schema GROUPID$PADNAME. A security manager controls access of them and it's forbidden for normal pads to include a $ in the name.
|
||||
|
||||
#### createPad(padID [, text])
|
||||
#### createPad(padID, [text], [authorId])
|
||||
* API >= 1
|
||||
* `authorId` in API >= 1.3.0
|
||||
|
||||
creates a new (non-group) pad. Note that if you need to create a group Pad, you should call **createGroupPad**.
|
||||
You get an error message if you use one of the following characters in the padID: "/", "?", "&" or "#".
|
||||
|
@ -519,8 +525,9 @@ copies a pad with full history and chat. If force is true and the destination pa
|
|||
* `{code: 0, message:"ok", data: null}`
|
||||
* `{code: 1, message:"padID does not exist", data: null}`
|
||||
|
||||
#### copyPadWithoutHistory(sourceID, destinationID[, force=false])
|
||||
#### copyPadWithoutHistory(sourceID, destinationID, [force=false], [authorId])
|
||||
* API >= 1.2.15
|
||||
* `authorId` in API >= 1.3.0
|
||||
|
||||
copies a pad without copying the history and chat. If force is true and the destination pad exists, it will be overwritten.
|
||||
Note that all the revisions will be lost! In most of the cases one should use `copyPad` API instead.
|
||||
|
|
|
@ -184,7 +184,7 @@ exports.getText = async (padID, rev) => {
|
|||
};
|
||||
|
||||
/**
|
||||
setText(padID, text) sets the text of a pad
|
||||
setText(padID, text, [authorId]) sets the text of a pad
|
||||
|
||||
Example returns:
|
||||
|
||||
|
@ -192,7 +192,7 @@ Example returns:
|
|||
{code: 1, message:"padID does not exist", data: null}
|
||||
{code: 1, message:"text too long", data: null}
|
||||
*/
|
||||
exports.setText = async (padID, text) => {
|
||||
exports.setText = async (padID, text, authorId = '') => {
|
||||
// text is required
|
||||
if (typeof text !== 'string') {
|
||||
throw new CustomError('text is not a string', 'apierror');
|
||||
|
@ -201,12 +201,12 @@ exports.setText = async (padID, text) => {
|
|||
// get the pad
|
||||
const pad = await getPadSafe(padID, true);
|
||||
|
||||
await pad.setText(text);
|
||||
await pad.setText(text, authorId);
|
||||
await padMessageHandler.updatePadClients(pad);
|
||||
};
|
||||
|
||||
/**
|
||||
appendText(padID, text) appends text to a pad
|
||||
appendText(padID, text, [authorId]) appends text to a pad
|
||||
|
||||
Example returns:
|
||||
|
||||
|
@ -214,14 +214,14 @@ Example returns:
|
|||
{code: 1, message:"padID does not exist", data: null}
|
||||
{code: 1, message:"text too long", data: null}
|
||||
*/
|
||||
exports.appendText = async (padID, text) => {
|
||||
exports.appendText = async (padID, text, authorId = '') => {
|
||||
// text is required
|
||||
if (typeof text !== 'string') {
|
||||
throw new CustomError('text is not a string', 'apierror');
|
||||
}
|
||||
|
||||
const pad = await getPadSafe(padID, true);
|
||||
await pad.appendText(text);
|
||||
await pad.appendText(text, authorId);
|
||||
await padMessageHandler.updatePadClients(pad);
|
||||
};
|
||||
|
||||
|
@ -258,14 +258,14 @@ exports.getHTML = async (padID, rev) => {
|
|||
};
|
||||
|
||||
/**
|
||||
setHTML(padID, html) sets the text of a pad based on 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}
|
||||
*/
|
||||
exports.setHTML = async (padID, html) => {
|
||||
exports.setHTML = async (padID, html, authorId = '') => {
|
||||
// html string is required
|
||||
if (typeof html !== 'string') {
|
||||
throw new CustomError('html is not a string', 'apierror');
|
||||
|
@ -276,7 +276,7 @@ exports.setHTML = async (padID, html) => {
|
|||
|
||||
// add a new changeset with the new html to the pad
|
||||
try {
|
||||
await importHtml.setPadHTML(pad, cleanText(html));
|
||||
await importHtml.setPadHTML(pad, cleanText(html), authorId);
|
||||
} catch (e) {
|
||||
throw new CustomError('HTML is malformed', 'apierror');
|
||||
}
|
||||
|
@ -459,14 +459,14 @@ exports.getLastEdited = async (padID) => {
|
|||
};
|
||||
|
||||
/**
|
||||
createPad(padName [, text]) creates a new pad in this group
|
||||
createPad(padName, [text], [authorId]) creates a new pad in this group
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: null}
|
||||
{code: 1, message:"pad does already exist", data: null}
|
||||
*/
|
||||
exports.createPad = async (padID, text) => {
|
||||
exports.createPad = async (padID, text, authorId = '') => {
|
||||
if (padID) {
|
||||
// ensure there is no $ in the padID
|
||||
if (padID.indexOf('$') !== -1) {
|
||||
|
@ -480,7 +480,7 @@ exports.createPad = async (padID, text) => {
|
|||
}
|
||||
|
||||
// create pad
|
||||
await getPadSafe(padID, false, text);
|
||||
await getPadSafe(padID, false, text, authorId);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -497,14 +497,14 @@ exports.deletePad = async (padID) => {
|
|||
};
|
||||
|
||||
/**
|
||||
restoreRevision(padID, [rev]) Restores revision from past as new changeset
|
||||
restoreRevision(padID, rev, [authorId]) Restores revision from past as new changeset
|
||||
|
||||
Example returns:
|
||||
|
||||
{code:0, message:"ok", data:null}
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.restoreRevision = async (padID, rev) => {
|
||||
exports.restoreRevision = async (padID, rev, authorId = '') => {
|
||||
// check if rev is a number
|
||||
if (rev === undefined) {
|
||||
throw new CustomError('rev is not defined', 'apierror');
|
||||
|
@ -555,7 +555,7 @@ exports.restoreRevision = async (padID, rev) => {
|
|||
|
||||
const changeset = builder.toString();
|
||||
|
||||
await pad.appendRevision(changeset);
|
||||
await pad.appendRevision(changeset, authorId);
|
||||
await padMessageHandler.updatePadClients(pad);
|
||||
};
|
||||
|
||||
|
@ -574,17 +574,17 @@ exports.copyPad = async (sourceID, destinationID, force) => {
|
|||
};
|
||||
|
||||
/**
|
||||
copyPadWithoutHistory(sourceID, destinationID[, force=false]) copies a pad. If force is true,
|
||||
the destination will be overwritten if it exists.
|
||||
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}
|
||||
*/
|
||||
exports.copyPadWithoutHistory = async (sourceID, destinationID, force) => {
|
||||
exports.copyPadWithoutHistory = async (sourceID, destinationID, force, authorId = '') => {
|
||||
const pad = await getPadSafe(sourceID, true);
|
||||
await pad.copyPadWithoutHistory(destinationID, force);
|
||||
await pad.copyPadWithoutHistory(destinationID, force, authorId);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -826,7 +826,7 @@ exports.getStats = async () => {
|
|||
const isInt = (value) => (parseFloat(value) === parseInt(value, 10)) && !isNaN(value);
|
||||
|
||||
// gets a pad safe
|
||||
const getPadSafe = async (padID, shouldExist, text) => {
|
||||
const getPadSafe = async (padID, shouldExist, text, authorId = '') => {
|
||||
// check if padID is a string
|
||||
if (typeof padID !== 'string') {
|
||||
throw new CustomError('padID is not a string', 'apierror');
|
||||
|
@ -851,7 +851,7 @@ const getPadSafe = async (padID, shouldExist, text) => {
|
|||
}
|
||||
|
||||
// pad exists, let's get it
|
||||
return padManager.getPad(padID, text);
|
||||
return padManager.getPad(padID, text, authorId);
|
||||
};
|
||||
|
||||
// checks if a rev is a legal number
|
||||
|
|
|
@ -103,7 +103,7 @@ exports.createGroupIfNotExistsFor = async (groupMapper) => {
|
|||
return result;
|
||||
};
|
||||
|
||||
exports.createGroupPad = async (groupID, padName, text) => {
|
||||
exports.createGroupPad = async (groupID, padName, text, authorId = '') => {
|
||||
// create the padID
|
||||
const padID = `${groupID}$${padName}`;
|
||||
|
||||
|
@ -123,7 +123,7 @@ exports.createGroupPad = async (groupID, padName, text) => {
|
|||
}
|
||||
|
||||
// create the pad
|
||||
await padManager.getPad(padID, text);
|
||||
await padManager.getPad(padID, text, authorId);
|
||||
|
||||
// create an entry in the group for this pad
|
||||
await db.setSub(`group:${groupID}`, ['pads', padID], 1);
|
||||
|
|
|
@ -134,8 +134,19 @@ version['1.2.15'] = Object.assign({}, version['1.2.14'],
|
|||
{copyPadWithoutHistory: ['sourceID', 'destinationID', 'force']}
|
||||
);
|
||||
|
||||
version['1.3.0'] = {
|
||||
...version['1.2.15'],
|
||||
appendText: ['padID', 'text', 'authorId'],
|
||||
copyPadWithoutHistory: ['sourceID', 'destinationID', 'force', 'authorId'],
|
||||
createGroupPad: ['groupID', 'padName', 'text', 'authorId'],
|
||||
createPad: ['padID', 'text', 'authorId'],
|
||||
restoreRevision: ['padID', 'rev', 'authorId'],
|
||||
setHTML: ['padID', 'html', 'authorId'],
|
||||
setText: ['padID', 'text', 'authorId'],
|
||||
};
|
||||
|
||||
// set the latest available API version here
|
||||
exports.latestApiVersion = '1.2.15';
|
||||
exports.latestApiVersion = '1.3.0';
|
||||
|
||||
// exports the versions so it can be used by the new Swagger endpoint
|
||||
exports.version = version;
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
'use strict';
|
||||
|
||||
const assert = require('assert').strict;
|
||||
const authorManager = require('../../../../node/db/AuthorManager');
|
||||
const common = require('../../common');
|
||||
const padManager = require('../../../../node/db/PadManager');
|
||||
|
||||
describe(__filename, function () {
|
||||
let agent;
|
||||
let authorId;
|
||||
let padId;
|
||||
let pad;
|
||||
|
||||
const restoreRevision = async (padId, rev) => {
|
||||
const restoreRevision = async (v, padId, rev, authorId = null) => {
|
||||
const p = new URLSearchParams(Object.entries({
|
||||
apikey: common.apiKey,
|
||||
padID: padId,
|
||||
rev,
|
||||
...(authorId == null ? {} : {authorId}),
|
||||
}));
|
||||
const res = await agent.get(`/api/1.2.11/restoreRevision?${p}`)
|
||||
const res = await agent.get(`/api/${v}/restoreRevision?${p}`)
|
||||
.expect(200)
|
||||
.expect('Content-Type', /json/);
|
||||
assert.equal(res.body.code, 0);
|
||||
|
@ -23,6 +26,8 @@ describe(__filename, function () {
|
|||
|
||||
before(async function () {
|
||||
agent = await common.init();
|
||||
authorId = await authorManager.getAuthor4Token('test-restoreRevision');
|
||||
assert(authorId);
|
||||
});
|
||||
|
||||
beforeEach(async function () {
|
||||
|
@ -38,14 +43,39 @@ describe(__filename, function () {
|
|||
if (await padManager.doesPadExist(padId)) await padManager.removePad(padId);
|
||||
});
|
||||
|
||||
// TODO: Enable once the end-of-pad newline bugs are fixed. See:
|
||||
// https://github.com/ether/etherpad-lite/pull/5253
|
||||
xit('content matches', async function () {
|
||||
const oldHead = pad.head;
|
||||
const wantAText = await pad.getInternalRevisionAText(pad.head - 1);
|
||||
assert(wantAText.text.endsWith('\nfoo\n'));
|
||||
await restoreRevision(padId, pad.head - 1);
|
||||
assert.equal(pad.head, oldHead + 1);
|
||||
assert.deepEqual(await pad.getInternalRevisionAText(pad.head), wantAText);
|
||||
describe('v1.2.11', function () {
|
||||
// TODO: Enable once the end-of-pad newline bugs are fixed. See:
|
||||
// https://github.com/ether/etherpad-lite/pull/5253
|
||||
xit('content matches', async function () {
|
||||
const oldHead = pad.head;
|
||||
const wantAText = await pad.getInternalRevisionAText(pad.head - 1);
|
||||
assert(wantAText.text.endsWith('\nfoo\n'));
|
||||
await restoreRevision('1.2.11', padId, pad.head - 1);
|
||||
assert.equal(pad.head, oldHead + 1);
|
||||
assert.deepEqual(await pad.getInternalRevisionAText(pad.head), wantAText);
|
||||
});
|
||||
|
||||
it('authorId ignored', async function () {
|
||||
const oldHead = pad.head;
|
||||
await restoreRevision('1.2.11', padId, pad.head - 1, authorId);
|
||||
assert.equal(pad.head, oldHead + 1);
|
||||
assert.equal(await pad.getRevisionAuthor(pad.head), '');
|
||||
});
|
||||
});
|
||||
|
||||
describe('v1.3.0', function () {
|
||||
it('change is attributed to given authorId', async function () {
|
||||
const oldHead = pad.head;
|
||||
await restoreRevision('1.3.0', padId, pad.head - 1, authorId);
|
||||
assert.equal(pad.head, oldHead + 1);
|
||||
assert.equal(await pad.getRevisionAuthor(pad.head), authorId);
|
||||
});
|
||||
|
||||
it('authorId can be omitted', async function () {
|
||||
const oldHead = pad.head;
|
||||
await restoreRevision('1.3.0', padId, pad.head - 1);
|
||||
assert.equal(pad.head, oldHead + 1);
|
||||
assert.equal(await pad.getRevisionAuthor(pad.head), '');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue