Эх сурвалжийг харах

Create a GET endpoint for getting doc lines

James Allen 12 жил өмнө
commit
6eb328e788

+ 4 - 0
services/docstore/.gitignore

@@ -0,0 +1,4 @@
+node_modules
+app/js/
+test/unit/js
+app.js

+ 89 - 0
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']
+
+

+ 23 - 0
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}")

+ 31 - 0
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

+ 14 - 0
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 })

+ 6 - 0
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]

+ 7 - 0
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
+

+ 11 - 0
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'

+ 25 - 0
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 <team@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"
+  }
+}

+ 111 - 0
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()

+ 53 - 0
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

+ 27 - 0
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