diff --git a/doc/api/http_api.md b/doc/api/http_api.md index 84a4ec7e..2ae674d8 100644 --- a/doc/api/http_api.md +++ b/doc/api/http_api.md @@ -61,7 +61,7 @@ Portal submits content into new blog post ## Usage ### API version -The latest version is `1.2.10` +The latest version is `1.2.11` The current version can be queried via /api. @@ -402,6 +402,33 @@ returns the number of revisions of this pad * `{code: 0, message:"ok", data: {revisions: 56}}` * `{code: 1, message:"padID does not exist", data: null}` +#### getSavedRevisionsCount(padID) + * API >= 1.2.11 + +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}` + +#### listSavedRevisions(padID) + * API >= 1.2.11 + +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}` + +#### saveRevision(padID [, rev]) + * API >= 1.2.11 + +saves a revision + +*Example returns:* + * `{code: 0, message:"ok", data: null}` + * `{code: 1, message:"padID does not exist", data: null}` + #### padUsersCount(padID) * API >= 1 diff --git a/src/node/db/API.js b/src/node/db/API.js index 81dedcfe..69a380c7 100644 --- a/src/node/db/API.js +++ b/src/node/db/API.js @@ -517,6 +517,117 @@ exports.getRevisionsCount = function(padID, callback) }); } +/** +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} +*/ +exports.getSavedRevisionsCount = function(padID, callback) +{ + //get the pad + getPadSafe(padID, true, function(err, pad) + { + if(ERR(err, callback)) return; + + callback(null, {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} +*/ +exports.listSavedRevisions = function(padID, callback) +{ + //get the pad + getPadSafe(padID, true, function(err, pad) + { + if(ERR(err, callback)) return; + + callback(null, {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} +*/ +exports.saveRevision = function(padID, rev, callback) +{ + //check if rev is set + if(typeof rev == "function") + { + callback = rev; + rev = undefined; + } + + //check if rev is a number + if(rev !== undefined && typeof rev != "number") + { + //try to parse the number + if(!isNaN(parseInt(rev))) + { + rev = parseInt(rev); + } + else + { + callback(new customError("rev is not a number", "apierror")); + return; + } + } + + //ensure this is not a negativ number + if(rev !== undefined && rev < 0) + { + callback(new customError("rev is a negativ number","apierror")); + return; + } + + //ensure this is not a float value + if(rev !== undefined && !is_int(rev)) + { + callback(new customError("rev is a float value","apierror")); + return; + } + + //get the pad + getPadSafe(padID, true, function(err, pad) + { + if(ERR(err, callback)) return; + + //the client asked for a special revision + if(rev !== undefined) + { + //check if this is a valid revision + if(rev > pad.getHeadRevisionNumber()) + { + callback(new customError("rev is higher than the head revision of the pad","apierror")); + return; + } + } else { + rev = pad.getHeadRevisionNumber(); + } + + authorManager.createAuthor('API', function(err, author) { + if(ERR(err, callback)) return; + + pad.addSavedRevision(rev, author.authorID, 'Saved through API call'); + callback(); + }); + }); +} + /** getLastEdited(padID) returns the timestamp of the last revision of the pad diff --git a/src/node/db/Pad.js b/src/node/db/Pad.js index 2f5860f8..53847600 100644 --- a/src/node/db/Pad.js +++ b/src/node/db/Pad.js @@ -54,6 +54,21 @@ Pad.prototype.getHeadRevisionNumber = function getHeadRevisionNumber() { return this.head; }; +Pad.prototype.getSavedRevisionsNumber = function getSavedRevisionsNumber() { + return this.savedRevisions.length; +}; + +Pad.prototype.getSavedRevisionsList = function getSavedRevisionsList() { + var savedRev = new Array(); + for(var rev in this.savedRevisions){ + savedRev.push(this.savedRevisions[rev].revNum); + } + savedRev.sort(function(a, b) { + return a - b; + }); + return savedRev; +}; + Pad.prototype.getPublicStatus = function getPublicStatus() { return this.publicStatus; }; diff --git a/src/node/handler/APIHandler.js b/src/node/handler/APIHandler.js index a26dd2cf..232b0b46 100644 --- a/src/node/handler/APIHandler.js +++ b/src/node/handler/APIHandler.js @@ -368,6 +368,9 @@ var version = , "setHTML" : ["padID", "html"] , "getAttributePool" : ["padID"] , "getRevisionsCount" : ["padID"] + , "getSavedRevisionsCount" : ["padID"] + , "listSavedRevisions" : ["padID"] + , "saveRevision" : ["padID", "rev"] , "getRevisionChangeset" : ["padID", "rev"] , "getLastEdited" : ["padID"] , "deletePad" : ["padID"] diff --git a/tests/backend/specs/api/pad.js b/tests/backend/specs/api/pad.js index 6010a11c..52849c2e 100644 --- a/tests/backend/specs/api/pad.js +++ b/tests/backend/specs/api/pad.js @@ -48,33 +48,38 @@ describe('Permission', function(){ -> deletePad -- This gives us a guaranteed clear environment -> createPad -> getRevisions -- Should be 0 - -> getHTML -- Should be the default pad text in HTML format - -> deletePad -- Should just delete a pad - -> getHTML -- Should return an error - -> createPad(withText) - -> getText -- Should have the text specified above as the pad text - -> setText - -> getText -- Should be the text set before - -> getRevisions -- Should be 0 still? - -> padUsersCount -- Should be 0 - -> getReadOnlyId -- Should be a value - -> listAuthorsOfPad(padID) -- should be empty array? - -> getLastEdited(padID) -- Should be when pad was made - -> setText(padId) - -> getLastEdited(padID) -- Should be when setText was performed - -> padUsers(padID) -- Should be when setText was performed - - -> setText(padId, "hello world") + -> getSavedRevisionsCount(padID) -- Should be 0 + -> listSavedRevisions(padID) -- Should be an empty array + -> getHTML -- Should be the default pad text in HTML format + -> deletePad -- Should just delete a pad + -> getHTML -- Should return an error + -> createPad(withText) + -> getText -- Should have the text specified above as the pad text + -> setText + -> getText -- Should be the text set before + -> getRevisions -- Should be 0 still? + -> saveRevision + -> getSavedRevisionsCount(padID) -- Should be 0 still? + -> listSavedRevisions(padID) -- Should be an empty array still ? + -> padUsersCount -- Should be 0 + -> getReadOnlyId -- Should be a value + -> listAuthorsOfPad(padID) -- should be empty array? -> getLastEdited(padID) -- Should be when pad was made - -> getText(padId) -- Should be "hello world" - -> movePad(padID, newPadId) -- Should provide consistant pad data - -> getText(newPadId) -- Should be "hello world" - -> movePad(newPadID, originalPadId) -- Should provide consistant pad data - -> getText(originalPadId) -- Should be "hello world" - -> getLastEdited(padID) -- Should not be 0 - -> setHTML(padID) -- Should fail on invalid HTML - -> setHTML(padID) *3 -- Should fail on invalid HTML - -> getHTML(padID) -- Should return HTML close to posted HTML + -> setText(padId) + -> getLastEdited(padID) -- Should be when setText was performed + -> padUsers(padID) -- Should be when setText was performed + + -> setText(padId, "hello world") + -> getLastEdited(padID) -- Should be when pad was made + -> getText(padId) -- Should be "hello world" + -> movePad(padID, newPadId) -- Should provide consistant pad data + -> getText(newPadId) -- Should be "hello world" + -> movePad(newPadID, originalPadId) -- Should provide consistant pad data + -> getText(originalPadId) -- Should be "hello world" + -> getLastEdited(padID) -- Should not be 0 + -> setHTML(padID) -- Should fail on invalid HTML + -> setHTML(padID) *3 -- Should fail on invalid HTML + -> getHTML(padID) -- Should return HTML close to posted HTML */ @@ -109,11 +114,35 @@ describe('getRevisionsCount', function(){ }); }) +describe('getSavedRevisionsCount', function(){ + it('gets saved revisions count of Pad', function(done) { + api.get(endPoint('getSavedRevisionsCount')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to get Saved Revisions Count"); + if(res.body.data.savedRevisions !== 0) throw new Error("Incorrect Saved Revisions Count"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('listSavedRevisions', function(){ + it('gets saved revision list of Pad', function(done) { + api.get(endPoint('listSavedRevisions')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to get Saved Revisions List"); + if(!res.body.data.savedRevisions.equals([])) throw new Error("Incorrect Saved Revisions List"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + describe('getHTML', function(){ it('get the HTML of Pad', function(done) { api.get(endPoint('getHTML')+"&padID="+testPadId) .expect(function(res){ - if(res.body.data.html.length <= 1) throw new Error("Unable to get Revision Count"); + if(res.body.data.html.length <= 1) throw new Error("Unable to get the HTML"); }) .expect('Content-Type', /json/) .expect(200, done) @@ -187,16 +216,50 @@ describe('getText', function(){ }) describe('getRevisionsCount', function(){ - it('gets Revision Coutn of a Pad', function(done) { + it('gets Revision Count of a Pad', function(done) { api.get(endPoint('getRevisionsCount')+"&padID="+testPadId) .expect(function(res){ - if(res.body.data.revisions !== 1) throw new Error("Unable to set text revision count") + if(res.body.data.revisions !== 1) throw new Error("Unable to get text revision count") }) .expect('Content-Type', /json/) .expect(200, done) }); }) +describe('saveRevision', function(){ + it('saves Revision', function(done) { + api.get(endPoint('saveRevision')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to save Revision"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('getSavedRevisionsCount', function(){ + it('gets saved revisions count of Pad', function(done) { + api.get(endPoint('getSavedRevisionsCount')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to get Saved Revisions Count"); + if(res.body.data.savedRevisions !== 1) throw new Error("Incorrect Saved Revisions Count"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) + +describe('listSavedRevisions', function(){ + it('gets saved revision list of Pad', function(done) { + api.get(endPoint('listSavedRevisions')+"&padID="+testPadId) + .expect(function(res){ + if(res.body.code !== 0) throw new Error("Unable to get Saved Revisions List"); + if(!res.body.data.savedRevisions.equals([1])) throw new Error("Incorrect Saved Revisions List"); + }) + .expect('Content-Type', /json/) + .expect(200, done) + }); +}) describe('padUsersCount', function(){ it('gets User Count of a Pad', function(done) { api.get(endPoint('padUsersCount')+"&padID="+testPadId) @@ -461,3 +524,25 @@ function generateLongText(){ } return text; } + +// Need this to compare arrays (listSavedRevisions test) +Array.prototype.equals = function (array) { + // if the other array is a falsy value, return + if (!array) + return false; + // compare lengths - can save a lot of time + if (this.length != array.length) + return false; + for (var i = 0, l=this.length; i < l; i++) { + // Check if we have nested arrays + if (this[i] instanceof Array && array[i] instanceof Array) { + // recurse into the nested arrays + if (!this[i].equals(array[i])) + return false; + } else if (this[i] != array[i]) { + // Warning - two different object instances will never be equal: {x:20} != {x:20} + return false; + } + } + return true; +}