/** * 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. */ var ERR = require("async-stacktrace"); var customError = require("../utils/customError"); var padManager = require("./PadManager"); var padMessageHandler = require("../handler/PadMessageHandler"); var readOnlyManager = require("./ReadOnlyManager"); var groupManager = require("./GroupManager"); var authorManager = require("./AuthorManager"); var sessionManager = require("./SessionManager"); var async = require("async"); var exportHtml = require("../utils/ExportHtml"); var importHtml = require("../utils/ImportHtml"); var cleanText = require("./Pad").cleanText; var PadDiff = require("../utils/padDiff"); /**********************/ /**GROUP FUNCTIONS*****/ /**********************/ exports.listAllGroups = groupManager.listAllGroups; exports.createGroup = groupManager.createGroup; exports.createGroupIfNotExistsFor = groupManager.createGroupIfNotExistsFor; exports.deleteGroup = groupManager.deleteGroup; exports.listPads = groupManager.listPads; exports.createGroupPad = groupManager.createGroupPad; /**********************/ /**PADLIST FUNCTION****/ /**********************/ exports.listAllPads = padManager.listAllPads; /**********************/ /**AUTHOR FUNCTIONS****/ /**********************/ exports.createAuthor = authorManager.createAuthor; exports.createAuthorIfNotExistsFor = authorManager.createAuthorIfNotExistsFor; exports.getAuthorName = authorManager.getAuthorName; exports.listPadsOfAuthor = authorManager.listPadsOfAuthor; exports.padUsers = padMessageHandler.padUsers; exports.padUsersCount = padMessageHandler.padUsersCount; /**********************/ /**SESSION FUNCTIONS***/ /**********************/ exports.createSession = sessionManager.createSession; exports.deleteSession = sessionManager.deleteSession; exports.getSessionInfo = sessionManager.getSessionInfo; exports.listSessionsOfGroup = sessionManager.listSessionsOfGroup; exports.listSessionsOfAuthor = sessionManager.listSessionsOfAuthor; /************************/ /**PAD CONTENT FUNCTIONS*/ /************************/ /** 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} */ exports.getText = 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; } //get the text of this revision pad.getInternalRevisionAText(rev, function(err, atext) { if(ERR(err, callback)) return; data = {text: atext.text}; callback(null, data); }) } //the client wants the latest text, lets return it to him else { callback(null, {"text": pad.text()}); } }); } /** setText(padID, text) 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} */ exports.setText = function(padID, text, callback) { //text is required if(typeof text != "string") { callback(new customError("text is no string","apierror")); return; } //get the pad getPadSafe(padID, true, function(err, pad) { if(ERR(err, callback)) return; //set the text pad.setText(text); //update the clients on the pad padMessageHandler.updatePadClients(pad, callback); }); } /** getHTML(padID, [rev]) returns the html of a pad Example returns: {code: 0, message:"ok", data: {text:"Welcome Text"}} {code: 1, message:"padID does not exist", data: null} */ exports.getHTML = function(padID, rev, callback) { if(typeof rev == "function") { callback = rev; rev = undefined; } if (rev !== undefined && typeof rev != "number") { if (!isNaN(parseInt(rev))) { rev = parseInt(rev); } else { callback(new customError("rev is not a number","apierror")); return; } } if(rev !== undefined && rev < 0) { callback(new customError("rev is a negative number","apierror")); return; } if(rev !== undefined && !is_int(rev)) { callback(new customError("rev is a float value","apierror")); return; } 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; } //get the html of this revision exportHtml.getPadHTML(pad, rev, function(err, html) { if(ERR(err, callback)) return; data = {html: html}; callback(null, data); }); } //the client wants the latest text, lets return it to him else { exportHtml.getPadHTML(pad, undefined, function (err, html) { if(ERR(err, callback)) return; data = {html: html}; callback(null, data); }); } }); } exports.setHTML = function(padID, html, callback) { //get the pad getPadSafe(padID, true, function(err, pad) { if(ERR(err, callback)) return; // add a new changeset with the new html to the pad importHtml.setPadHTML(pad, cleanText(html)); //update the clients on the pad padMessageHandler.updatePadClients(pad, callback); }); } /******************/ /**CHAT FUNCTIONS */ /******************/ /** getChatHistory(padId, start, end), returns a part of or the whole chat-history of this pad Example returns: {"code":0,"message":"ok","data":{"messages":[{"text":"foo","userId":"a.foo","time":1359199533759,"userName":"test"}, {"text":"bar","userId":"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} */ exports.getChatHistory = function(padID, start, end, callback) { if(start && end) { if(start < 0) { callback(new customError("start is below zero","apierror")); return; } if(end < 0) { callback(new customError("end is below zero","apierror")); return; } if(start > end) { callback(new customError("start is higher than end","apierror")); return; } } //get the pad getPadSafe(padID, true, function(err, pad) { if(ERR(err, callback)) return; var chatHead = pad.chatHead; // fall back to getting the whole chat-history if a parameter is missing if(!start || !end) { start = 0; end = pad.chatHead; } if(start >= chatHead && chatHead > 0) { callback(new customError("start is higher or equal to the current chatHead","apierror")); return; } if(end > chatHead) { callback(new customError("end is higher than the current chatHead","apierror")); return; } // the the whole message-log and return it to the client pad.getChatMessages(start, end, function(err, msgs) { if(ERR(err, callback)) return; callback(null, {messages: msgs}); }); }); } /*****************/ /**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} */ exports.getRevisionsCount = function(padID, callback) { //get the pad getPadSafe(padID, true, function(err, pad) { if(ERR(err, callback)) return; callback(null, {revisions: pad.getHeadRevisionNumber()}); }); } /** 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} */ exports.getLastEdited = function(padID, callback) { //get the pad getPadSafe(padID, true, function(err, pad) { if(ERR(err, callback)) return; pad.getLastEdit(function(err, value) { if(ERR(err, callback)) return; callback(null, {lastEdited: value}); }); }); } /** createPad(padName [, text]) 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 = function(padID, text, callback) { //ensure there is no $ in the padID if(padID && padID.indexOf("$") != -1) { callback(new customError("createPad can't create group pads","apierror")); return; } //create pad getPadSafe(padID, false, text, function(err) { if(ERR(err, callback)) return; callback(); }); } /** deletePad(padID) deletes a pad Example returns: {code: 0, message:"ok", data: null} {code: 1, message:"padID does not exist", data: null} */ exports.deletePad = function(padID, callback) { getPadSafe(padID, true, function(err, pad) { if(ERR(err, callback)) return; pad.remove(callback); }); } /** 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} */ exports.getReadOnlyID = function(padID, callback) { //we don't need the pad object, but this function does all the security stuff for us getPadSafe(padID, true, function(err) { if(ERR(err, callback)) return; //get the readonlyId readOnlyManager.getReadOnlyId(padID, function(err, readOnlyId) { if(ERR(err, callback)) return; callback(null, {readOnlyID: readOnlyId}); }); }); } /** 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} */ exports.setPublicStatus = function(padID, publicStatus, callback) { //ensure this is a group pad if(padID && padID.indexOf("$") == -1) { callback(new customError("You can only get/set the publicStatus of pads that belong to a group","apierror")); return; } //get the pad getPadSafe(padID, true, function(err, pad) { if(ERR(err, callback)) return; //convert string to boolean if(typeof publicStatus == "string") publicStatus = publicStatus == "true" ? true : false; //set the password pad.setPublicStatus(publicStatus); callback(); }); } /** getPublicStatus(padID) return true of false Example returns: {code: 0, message:"ok", data: {publicStatus: true}} {code: 1, message:"padID does not exist", data: null} */ exports.getPublicStatus = function(padID, callback) { //ensure this is a group pad if(padID && padID.indexOf("$") == -1) { callback(new customError("You can only get/set the publicStatus of pads that belong to a group","apierror")); return; } //get the pad getPadSafe(padID, true, function(err, pad) { if(ERR(err, callback)) return; callback(null, {publicStatus: pad.getPublicStatus()}); }); } /** setPassword(padID, password) returns ok or a error message Example returns: {code: 0, message:"ok", data: null} {code: 1, message:"padID does not exist", data: null} */ exports.setPassword = function(padID, password, callback) { //ensure this is a group pad if(padID && padID.indexOf("$") == -1) { callback(new customError("You can only get/set the password of pads that belong to a group","apierror")); return; } //get the pad getPadSafe(padID, true, function(err, pad) { if(ERR(err, callback)) return; //set the password pad.setPassword(password == "" ? null : password); callback(); }); } /** isPasswordProtected(padID) returns true or false Example returns: {code: 0, message:"ok", data: {passwordProtection: true}} {code: 1, message:"padID does not exist", data: null} */ exports.isPasswordProtected = function(padID, callback) { //ensure this is a group pad if(padID && padID.indexOf("$") == -1) { callback(new customError("You can only get/set the password of pads that belong to a group","apierror")); return; } //get the pad getPadSafe(padID, true, function(err, pad) { if(ERR(err, callback)) return; callback(null, {isPasswordProtected: pad.isPasswordProtected()}); }); } /** 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} */ exports.listAuthorsOfPad = function(padID, callback) { //get the pad getPadSafe(padID, true, function(err, pad) { if(ERR(err, callback)) return; callback(null, {authorIDs: pad.getAllAuthors()}); }); } /** 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: , time: