From 6eb328e788e0e1813259dbc85ec3e618743f90e7 Mon Sep 17 00:00:00 2001 From: James Allen Date: Mon, 28 Apr 2014 16:45:59 +0100 Subject: [PATCH] Create a GET endpoint for getting doc lines --- services/docstore/.gitignore | 4 + services/docstore/Gruntfile.coffee | 89 ++++++++++++++ services/docstore/app.coffee | 23 ++++ .../docstore/app/coffee/DocManager.coffee | 31 +++++ .../docstore/app/coffee/HttpController.coffee | 14 +++ .../docstore/app/coffee/MongoManager.coffee | 6 + services/docstore/app/coffee/mongojs.coffee | 7 ++ .../docstore/config/settings.defaults.coffee | 11 ++ services/docstore/package.json | 25 ++++ .../test/unit/coffee/DocManagerTests.coffee | 111 ++++++++++++++++++ .../unit/coffee/HttpControllerTests.coffee | 53 +++++++++ .../test/unit/coffee/MongoManagerTests.coffee | 27 +++++ 12 files changed, 401 insertions(+) create mode 100644 services/docstore/.gitignore create mode 100644 services/docstore/Gruntfile.coffee create mode 100644 services/docstore/app.coffee create mode 100644 services/docstore/app/coffee/DocManager.coffee create mode 100644 services/docstore/app/coffee/HttpController.coffee create mode 100644 services/docstore/app/coffee/MongoManager.coffee create mode 100644 services/docstore/app/coffee/mongojs.coffee create mode 100644 services/docstore/config/settings.defaults.coffee create mode 100644 services/docstore/package.json create mode 100644 services/docstore/test/unit/coffee/DocManagerTests.coffee create mode 100644 services/docstore/test/unit/coffee/HttpControllerTests.coffee create mode 100644 services/docstore/test/unit/coffee/MongoManagerTests.coffee diff --git a/services/docstore/.gitignore b/services/docstore/.gitignore new file mode 100644 index 0000000000..6617e40b2a --- /dev/null +++ b/services/docstore/.gitignore @@ -0,0 +1,4 @@ +node_modules +app/js/ +test/unit/js +app.js diff --git a/services/docstore/Gruntfile.coffee b/services/docstore/Gruntfile.coffee new file mode 100644 index 0000000000..6c4eaf711b --- /dev/null +++ b/services/docstore/Gruntfile.coffee @@ -0,0 +1,89 @@ +spawn = require("child_process").spawn + +module.exports = (grunt) -> + grunt.initConfig + coffee: + app_src: + expand: true, + flatten: true, + cwd: "app" + src: ['coffee/*.coffee'], + dest: 'app/js/', + ext: '.js' + + app: + src: "app.coffee" + dest: "app.js" + + unit_tests: + expand: true + cwd: "test/unit/coffee" + src: ["**/*.coffee"] + dest: "test/unit/js/" + ext: ".js" + + acceptance_tests: + expand: true + cwd: "test/acceptance/coffee" + src: ["**/*.coffee"] + dest: "test/acceptance/js/" + ext: ".js" + + smoke_tests: + expand: true + cwd: "test/smoke/coffee" + src: ["**/*.coffee"] + dest: "test/smoke/js" + ext: ".js" + + clean: + app: ["app/js/"] + unit_tests: ["test/unit/js"] + acceptance_tests: ["test/acceptance/js"] + smoke_tests: ["test/smoke/js"] + + execute: + app: + src: "app.js" + + mochaTest: + unit: + options: + reporter: "spec" + src: ["test/unit/js/**/*.js"] + acceptance: + options: + reporter: "spec" + timeout: 40000 + grep: grunt.option("grep") + src: ["test/acceptance/js/**/*.js"] + smoke: + options: + reported: "spec" + timeout: 10000 + src: ["test/smoke/js/**/*.js"] + + grunt.loadNpmTasks 'grunt-contrib-coffee' + grunt.loadNpmTasks 'grunt-contrib-clean' + grunt.loadNpmTasks 'grunt-mocha-test' + grunt.loadNpmTasks 'grunt-shell' + grunt.loadNpmTasks 'grunt-execute' + grunt.loadNpmTasks 'grunt-bunyan' + + grunt.registerTask 'compile:app', ['clean:app', 'coffee:app', 'coffee:app_src', 'coffee:smoke_tests'] + grunt.registerTask 'run', ['compile:app', 'bunyan', 'execute'] + + grunt.registerTask 'compile:unit_tests', ['clean:unit_tests', 'coffee:unit_tests'] + grunt.registerTask 'test:unit', ['compile:app', 'compile:unit_tests', 'mochaTest:unit'] + + grunt.registerTask 'compile:acceptance_tests', ['clean:acceptance_tests', 'coffee:acceptance_tests'] + grunt.registerTask 'test:acceptance', ['compile:acceptance_tests', 'mochaTest:acceptance'] + + grunt.registerTask 'compile:smoke_tests', ['clean:smoke_tests', 'coffee:smoke_tests'] + grunt.registerTask 'test:smoke', ['compile:smoke_tests', 'mochaTest:smoke'] + + grunt.registerTask 'install', 'compile:app' + + grunt.registerTask 'default', ['run'] + + diff --git a/services/docstore/app.coffee b/services/docstore/app.coffee new file mode 100644 index 0000000000..b3932ffd46 --- /dev/null +++ b/services/docstore/app.coffee @@ -0,0 +1,23 @@ +Settings = require('settings-sharelatex') +logger = require('logger-sharelatex') +logger.initialize("docstore") + +express = require('express') +HttpController = require "./app/js/HttpController" + +app = express() + +app.get '/project/:project_id/doc/:doc_id', HttpController.getDoc + +app.get '/status', (req, res)-> + res.send('docstore is alive') + +app.use (error, req, res, next) -> + logger.error err: error, "request errored" + res.send(500, "Oops, something went wrong") + +port = Settings.internal.docstore.port +host = Settings.internal.docstore.host +app.listen port, host, (error) -> + throw error if error? + logger.log("docstore listening on #{host}:#{port}") diff --git a/services/docstore/app/coffee/DocManager.coffee b/services/docstore/app/coffee/DocManager.coffee new file mode 100644 index 0000000000..201868f882 --- /dev/null +++ b/services/docstore/app/coffee/DocManager.coffee @@ -0,0 +1,31 @@ +MongoManager = require "./MongoManager" + +module.exports = DocManager = + getDoc: (project_id, doc_id, callback = (error, doc) ->) -> + MongoManager.findProject project_id, (error, project) -> + return callback(error) if error? + return callback null, null if !project? + DocManager.findDocInProject project, doc_id, (error, doc) -> + return callback(error) if error? + return callback null, doc + + findDocInProject: (project, doc_id, callback = (error, doc, mongoPath) ->) -> + result = @_findDocInFolder project.rootFolder[0], doc_id, "rootFolder.0" + if result? + callback null, result.doc, result.mongoPath + else + callback null, null, null + + _findDocInFolder: (folder, doc_id, currentPath) -> + for doc, i in folder.docs or [] + if doc._id.toString() == doc_id.toString() + return { + doc: doc + mongoPath: "#{currentPath}.docs.#{i}" + } + + for childFolder, i in folder.folders or [] + result = @_findDocInFolder childFolder, doc_id, "#{currentPath}.folders.#{i}" + return result if result? + + return null \ No newline at end of file diff --git a/services/docstore/app/coffee/HttpController.coffee b/services/docstore/app/coffee/HttpController.coffee new file mode 100644 index 0000000000..d3eef4fa7d --- /dev/null +++ b/services/docstore/app/coffee/HttpController.coffee @@ -0,0 +1,14 @@ +DocManager = require "./DocManager" +logger = require "logger-sharelatex" + +module.exports = HttpController = + getDoc: (req, res, next = (error) ->) -> + project_id = req.params.project_id + doc_id = req.params.doc_id + logger.log project_id: project_id, doc_id: doc_id, "getting doc" + DocManager.getDoc project_id, doc_id, (error, doc) -> + return next(error) if error? + if !doc? + res.send 404 + else + res.send JSON.stringify({ lines: doc.lines }) \ No newline at end of file diff --git a/services/docstore/app/coffee/MongoManager.coffee b/services/docstore/app/coffee/MongoManager.coffee new file mode 100644 index 0000000000..a3f97f73b1 --- /dev/null +++ b/services/docstore/app/coffee/MongoManager.coffee @@ -0,0 +1,6 @@ +{db, ObjectId} = require "./mongojs" + +module.exports = MongoManager = + findProject: (project_id, callback = (error, project) ->) -> + db.projects.find _id: ObjectId(project_id.toString()), {}, (error, projects = []) -> + callback error, projects[0] \ No newline at end of file diff --git a/services/docstore/app/coffee/mongojs.coffee b/services/docstore/app/coffee/mongojs.coffee new file mode 100644 index 0000000000..86ce73809a --- /dev/null +++ b/services/docstore/app/coffee/mongojs.coffee @@ -0,0 +1,7 @@ +Settings = require "settings-sharelatex" +mongojs = require "mongojs" +db = mongojs.connect(Settings.mongo.url, ["projects"]) +module.exports = + db: db + ObjectId: mongojs.ObjectId + diff --git a/services/docstore/config/settings.defaults.coffee b/services/docstore/config/settings.defaults.coffee new file mode 100644 index 0000000000..8a3fc9197f --- /dev/null +++ b/services/docstore/config/settings.defaults.coffee @@ -0,0 +1,11 @@ +http = require('http') +http.globalAgent.maxSockets = 300 + +module.exports = + internal: + docstore: + port: 3016 + host: "localhost" + + mongo: + url: 'mongodb://127.0.0.1/sharelatex' diff --git a/services/docstore/package.json b/services/docstore/package.json new file mode 100644 index 0000000000..efad89535d --- /dev/null +++ b/services/docstore/package.json @@ -0,0 +1,25 @@ +{ + "name": "docstore-sharelatex", + "version": "0.0.0", + "description": "A CRUD API for handling text documents in projects", + "author": "ShareLaTeX ", + "dependencies": { + "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#master", + "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master", + "mongojs": "0.9.11", + "express": "~4.1.1" + }, + "devDependencies": { + "grunt-execute": "~0.2.1", + "grunt-contrib-clean": "~0.5.0", + "grunt-shell": "~0.7.0", + "grunt-contrib-coffee": "~0.10.1", + "grunt-mocha-test": "~0.10.2", + "grunt": "~0.4.4", + "bunyan": "~0.22.3", + "grunt-bunyan": "~0.5.0", + "sinon": "~1.5.2", + "sandboxed-module": "~0.3.0", + "chai": "~1.9.1" + } +} diff --git a/services/docstore/test/unit/coffee/DocManagerTests.coffee b/services/docstore/test/unit/coffee/DocManagerTests.coffee new file mode 100644 index 0000000000..7afc546964 --- /dev/null +++ b/services/docstore/test/unit/coffee/DocManagerTests.coffee @@ -0,0 +1,111 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +chai = require('chai') +chai.should() +expect = chai.expect +modulePath = require('path').join __dirname, '../../../app/js/DocManager' +ObjectId = require("mongojs").ObjectId + +describe "DocManager", -> + beforeEach -> + @DocManager = SandboxedModule.require modulePath, requires: + "./MongoManager": @MongoManager = {} + @doc_id = ObjectId().toString() + @project_id = ObjectId().toString() + @callback = sinon.stub() + + describe "getDoc", -> + describe "when the project exists", -> + beforeEach -> + @project = { name: "mock-project" } + @doc = { _id: @doc_id, lines: ["mock-lines"] } + @MongoManager.findProject = sinon.stub().callsArgWith(1, null, @project) + @DocManager.findDocInProject = sinon.stub().callsArgWith(2, null, @doc) + @DocManager.getDoc @project_id, @doc_id, @callback + + it "should get the project from the database", -> + @MongoManager.findProject + .calledWith(@project_id) + .should.equal true + + it "should find the doc in the project", -> + @DocManager.findDocInProject + .calledWith(@project, @doc_id) + .should.equal true + + it "should return the doc", -> + @callback.calledWith(null, @doc).should.equal true + + describe "when the project does not exist", -> + beforeEach -> + @MongoManager.findProject = sinon.stub().callsArgWith(1, null, @null) + @DocManager.findDocInProject = sinon.stub() + @DocManager.getDoc @project_id, @doc_id, @callback + + it "should not try to find the doc in the project", -> + @DocManager.findDocInProject.called.should.equal false + + it "should return null", -> + @callback.calledWith(null, null).should.equal true + + describe "findDocInProject", -> + it "should find the doc when it is in the root folder", (done) -> + @DocManager.findDocInProject { + rootFolder: [{ + docs: [{ + _id: ObjectId(@doc_id) + }] + }] + }, @doc_id, (error, doc, mongoPath) => + expect(doc).to.deep.equal { _id: ObjectId(@doc_id) } + mongoPath.should.equal "rootFolder.0.docs.0" + done() + + it "should find the doc when it is in a sub folder", (done) -> + @DocManager.findDocInProject { + rootFolder: [{ + folders: [{ + docs: [{ + _id: ObjectId(@doc_id) + }] + }] + }] + }, @doc_id, (error, doc, mongoPath) => + expect(doc).to.deep.equal { _id: ObjectId(@doc_id) } + mongoPath.should.equal "rootFolder.0.folders.0.docs.0" + done() + + it "should find the doc when it there are other docs", (done) -> + @DocManager.findDocInProject { + rootFolder: [{ + folders: [{ + docs: [{ + _id: ObjectId() + }] + }, { + docs: [{ + _id: ObjectId() + }, { + _id: ObjectId(@doc_id) + }] + }], + docs: [{ + _id: ObjectId() + }] + }] + }, @doc_id, (error, doc, mongoPath) => + expect(doc).to.deep.equal { _id: ObjectId(@doc_id) } + mongoPath.should.equal "rootFolder.0.folders.1.docs.1" + done() + + it "should return null when the doc doesn't exist", (done) -> + @DocManager.findDocInProject { + rootFolder: [{ + folders: [{ + docs: [] + }] + }] + }, @doc_id, (error, doc, mongoPath) => + expect(doc).to.be.null + expect(mongoPath).to.be.null + done() diff --git a/services/docstore/test/unit/coffee/HttpControllerTests.coffee b/services/docstore/test/unit/coffee/HttpControllerTests.coffee new file mode 100644 index 0000000000..5dfaf4de7c --- /dev/null +++ b/services/docstore/test/unit/coffee/HttpControllerTests.coffee @@ -0,0 +1,53 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +chai = require('chai') +chai.should() +expect = chai.expect +modulePath = require('path').join __dirname, '../../../app/js/HttpController' + +describe "HttpController", -> + beforeEach -> + @HttpController = SandboxedModule.require modulePath, requires: + "./DocManager": @DocManager = {} + "logger-sharelatex": @logger = { log: sinon.stub() } + @res = { send: sinon.stub() } + @req = {} + @next = sinon.stub() + @project_id = "mock-project-id" + @doc_id = "mock-doc-id" + @doc = { + _id: @doc_id + lines: ["mock", "lines"] + } + + describe "getDoc", -> + describe "when the doc exists", -> + beforeEach -> + @req.params = + project_id: @project_id + doc_id: @doc_id + @DocManager.getDoc = sinon.stub().callsArgWith(2, null, @doc) + @HttpController.getDoc @req, @res, @next + + it "should get the document", -> + @DocManager.getDoc + .calledWith(@project_id, @doc_id) + .should.equal true + + it "should return the doc as JSON", -> + @res.send + .calledWith(JSON.stringify(lines: @doc.lines)) + .should.equal true + + describe "when the doc does not exist", -> + beforeEach -> + @req.params = + project_id: @project_id + doc_id: @doc_id + @DocManager.getDoc = sinon.stub().callsArgWith(2, null, null) + @HttpController.getDoc @req, @res, @next + + it "should return a 404", -> + @res.send + .calledWith(404) + .should.equal true \ No newline at end of file diff --git a/services/docstore/test/unit/coffee/MongoManagerTests.coffee b/services/docstore/test/unit/coffee/MongoManagerTests.coffee new file mode 100644 index 0000000000..b3706004f1 --- /dev/null +++ b/services/docstore/test/unit/coffee/MongoManagerTests.coffee @@ -0,0 +1,27 @@ +SandboxedModule = require('sandboxed-module') +sinon = require('sinon') +require('chai').should() +modulePath = require('path').join __dirname, '../../../app/js/MongoManager' +ObjectId = require("mongojs").ObjectId + +describe "MongoManager", -> + beforeEach -> + @MongoManager = SandboxedModule.require modulePath, requires: + "./mongojs": + db: @db = { projects: {} } + ObjectId: ObjectId + @project_id = ObjectId().toString() + @callback = sinon.stub() + + describe "findProject", -> + beforeEach -> + @project = { name: "mock-project" } + @db.projects.find = sinon.stub().callsArgWith(2, null, [@project]) + @MongoManager.findProject @project_id, @callback + + it "should find the project without the doc lines", -> + @db.projects.find + .calledWith({ + _id: ObjectId(@project_id) + }, {}) + .should.equal true