Просмотр исходного кода

Set up beginnings of getContacts end point

James Allen 11 лет назад
Родитель
Сommit
ededf6f6b9

+ 13 - 1
services/contacts/app/coffee/ContactManager.coffee

@@ -15,4 +15,16 @@ module.exports = ContactManager =
 			user_id: user_id
 		}, update, {
 			upsert: true
-		}, callback)
+		}, callback)
+	
+	getContacts: (user_id, callback = (error) ->) ->
+		try
+			user_id = ObjectId(user_id.toString())
+		catch error
+			return callback error
+		
+		db.contacts.findOne {
+			user_id: user_id
+		}, (error, user) ->
+			return callback(error) if error?
+			callback null, user?.contacts

+ 62 - 2
services/contacts/app/coffee/HttpController.coffee

@@ -1,5 +1,7 @@
 ContactManager = require "./ContactManager"
+WebApiManager = require "./WebApiManager"
 logger = require "logger-sharelatex"
+async = require "async"
 
 module.exports = HttpController =
 	addContact: (req, res, next) ->
@@ -18,5 +20,63 @@ module.exports = HttpController =
 				return next(error) if error?
 				res.status(204).end()
 
-	getUserContacts: (req, res, next) ->
-		
+	CONTACT_LIMIT: 50
+	getContacts: (req, res, next) ->
+		{user_id} = req.params
+
+		if req.query?.limit?
+			limit = parseInt(req.query.limit, 10)
+		else
+			limit = HttpController.CONTACT_LIMIT
+		limit = Math.min(limit, HttpController.CONTACT_LIMIT)
+
+		logger.log {user_id}, "getting contacts"
+
+		ContactManager.getContacts user_id, (error, contact_dict) ->
+			return next(error) if error?
+			
+			contacts = []
+			for user_id, data of (contact_dict or {})
+				contacts.push {
+					user_id: user_id
+					n: data.n
+					ts: data.ts
+				}
+
+			HttpController._sortContacts contacts
+			contacts = contacts.slice(0, limit)
+
+			async.mapLimit contacts, 5,
+				(contact, cb) ->
+					WebApiManager.getUserDetails contact.user_id, (error, user) ->
+						return cb(error) if error?
+						cb null, HttpController._formatUser user
+				(error, users) ->
+					return next(error) if error?
+					res.status(200).send({
+						contacts: users
+					})
+	
+	_sortContacts: (contacts) ->
+		contacts.sort (a, b) ->
+			# Sort by decreasing count, descreasing timestamp.
+			# I.e. biggest count, and most recent at front.
+			if a.n > b.n
+				return -1
+			else if a.n < b.n
+				return 1
+			else
+				if a.ts > b.ts
+					return -1
+				else if a.ts < b.ts
+					return 1
+				else
+					return 0
+	
+	_formatUser: (user) ->
+		return {
+			id: user._id
+			email: user.email
+			first_name: user.first_name
+			last_name: user.last_name
+		}

+ 31 - 1
services/contacts/test/unit/coffee/ContactsManagerTests.coffee

@@ -35,13 +35,14 @@ describe "ContactManager", ->
 					.calledWith({
 						user_id: ObjectId(@user_id)
 					}, {
-						$set:
+						$inc:
 							"contacts.mock_contact.n": 1
 						$set:
 							"contacts.mock_contact.ts": new Date()
 					}, {
 						upsert: true
 					})
+					.should.equal true
 			
 			it "should call the callback", ->
 				@callback.called.should.equal true
@@ -52,3 +53,32 @@ describe "ContactManager", ->
 			
 			it "should call the callback with an error", ->
 				@callback.calledWith(new Error()).should.equal true
+	
+	describe "getContacts", ->
+		beforeEach ->
+			@user = {
+				contacts: ["mock", "contacts"]
+			}
+			@db.contacts.findOne = sinon.stub().callsArgWith(1, null, @user)
+		
+		describe "with a valid user_id", ->
+			beforeEach ->
+				@ContactManager.getContacts @user_id, @callback
+			
+			it "should find the user's contacts", ->
+				@db.contacts.findOne
+					.calledWith({
+						user_id: ObjectId(@user_id)
+					})
+					.should.equal true
+			
+			it "should call the callback with the contacts", ->
+				@callback.calledWith(null, @user.contacts).should.equal true
+		
+		describe "with an invalid user id", ->
+			beforeEach ->
+				@ContactManager.getContacts "not-valid-object-id", @callback
+			
+			it "should call the callback with an error", ->
+				@callback.calledWith(new Error()).should.equal true
+		

+ 78 - 0
services/contacts/test/unit/coffee/HttpControllerTests.coffee

@@ -9,6 +9,7 @@ describe "HttpController", ->
 	beforeEach ->
 		@HttpController = SandboxedModule.require modulePath, requires:
 			"./ContactManager": @ContactManager = {}
+			"./WebApiManager": @WebApiManager = {}
 			"logger-sharelatex": @logger = { log: sinon.stub() }
 		@user_id = "mock-user-id"
 		@contact_id = "mock-contact-id"
@@ -55,3 +56,80 @@ describe "HttpController", ->
 			it "should return 400, Bad Request", ->
 				@res.status.calledWith(400).should.equal true
 				@res.send.calledWith("contact_id should be a non-blank string").should.equal true
+
+	describe "getContacts", ->
+		beforeEach ->
+			@req.params =
+				user_id: @user_id
+			now = Date.now()
+			@contacts = {
+				"user-id-1": { n: 2, ts: new Date(now) }
+				"user-id-2": { n: 4, ts: new Date(now) }
+				"user-id-3": { n: 2, ts: new Date(now - 1000) }
+			}
+			@user_details = {
+				"user-id-1": { _id: "user-id-1", email: "joe@example.com", first_name: "Joe", last_name: "Example", extra: "foo" }
+				"user-id-2": { _id: "user-id-2", email: "jane@example.com", first_name: "Sarah", last_name: "Example", extra: "foo" }
+				"user-id-3": { _id: "user-id-3", email: "sam@example.com", first_name: "Sam", last_name: "Example", extra: "foo" }
+			}
+			@ContactManager.getContacts = sinon.stub().callsArgWith(1, null, @contacts)
+			@WebApiManager.getUserDetails = (user_id, callback = (error, user) ->) =>
+				callback null, @user_details[user_id]
+			sinon.spy @WebApiManager, "getUserDetails"
+
+		describe "normally", ->
+			beforeEach ->
+				@HttpController.getContacts @req, @res, @next
+				
+			it "should look up the contacts in mongo", ->
+				@ContactManager.getContacts
+					.calledWith(@user_id)
+					.should.equal true
+			
+			it "should look up each contact in web for their details", ->
+				for user_id, data of @contacts
+					@WebApiManager.getUserDetails
+						.calledWith(user_id)
+						.should.equal true
+			
+			it "should return a sorted list of contacts by count and timestamp", ->
+				@res.send
+					.calledWith({
+						contacts: [
+							{ id: "user-id-2", email: "jane@example.com", first_name: "Sarah", last_name: "Example" }
+							{ id: "user-id-1", email: "joe@example.com", first_name: "Joe", last_name: "Example" }
+							{ id: "user-id-3", email: "sam@example.com", first_name: "Sam", last_name: "Example" }
+						]
+					})
+					.should.equal true
+		
+		describe "with more contacts than the limit", ->
+			beforeEach ->
+				@req.query =
+					limit: 2
+				@HttpController.getContacts @req, @res, @next
+
+			it "should return the most commonly used contacts up to the limit", ->
+				@res.send
+					.calledWith({
+						contacts: [
+							{ id: "user-id-2", email: "jane@example.com", first_name: "Sarah", last_name: "Example" }
+							{ id: "user-id-1", email: "joe@example.com", first_name: "Joe", last_name: "Example" }
+						]
+					})
+					.should.equal true
+		
+		describe "without a contact list", ->
+			beforeEach ->
+				@ContactManager.getContacts = sinon.stub().callsArgWith(1, null, null)
+				@HttpController.getContacts @req, @res, @next
+
+			it "should return an empty list", ->
+				@res.send
+					.calledWith({
+						contacts: []
+					})
+					.should.equal true
+		
+		describe "with a holding account", ->
+			it "should not return holding accounts"