├── Procfile
├── views
├── img
│ └── neo4j-swagger.jpg
├── layout.jade
├── users.jade
├── index.jade
└── user.jade
├── routes
├── site.js
├── index.js
├── pets.js
└── users.js
├── models
├── neo4j
│ ├── category.js
│ ├── user.js
│ └── pet.js
├── swagger_models.js
├── oldpets.js
├── pets.js
└── users.js
├── .gitignore
├── LICENSE
├── package.json
├── README.md
└── app.js
/Procfile:
--------------------------------------------------------------------------------
1 | web: node app.js
--------------------------------------------------------------------------------
/views/img/neo4j-swagger.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tinj/node-neo4j-swagger-api/HEAD/views/img/neo4j-swagger.jpg
--------------------------------------------------------------------------------
/routes/site.js:
--------------------------------------------------------------------------------
1 | /*
2 | * GET home page.
3 | */
4 |
5 | exports.index = function(req, res){
6 | res.render('index');
7 | };
--------------------------------------------------------------------------------
/routes/index.js:
--------------------------------------------------------------------------------
1 | // convenience wrapper around all other files:
2 | exports.site = require('./site');
3 | exports.users = require('./users');
4 | exports.pets = require('./pets');
--------------------------------------------------------------------------------
/models/neo4j/category.js:
--------------------------------------------------------------------------------
1 | // extracts just the data from the query results
2 |
3 | var _ = require('underscore');
4 |
5 | var Category = module.exports = function (_node) {
6 | _(this).extend(_node.data);
7 | };
--------------------------------------------------------------------------------
/models/neo4j/user.js:
--------------------------------------------------------------------------------
1 | // extracts just the data from the query results
2 |
3 | var _ = require('underscore');
4 |
5 | var User = module.exports = function (_node) {
6 | _(this).extend(_node.data);
7 | };
8 |
9 | User.prototype.friends = function (friends) {
10 | if (friends && friends.length) {
11 | this.friends = friends;
12 | }
13 | return this.friends;
14 | };
--------------------------------------------------------------------------------
/views/layout.jade:
--------------------------------------------------------------------------------
1 | doctype 5
2 | html
3 | head
4 | title= title
5 | link(rel='stylesheet', href='/stylesheets/style.css')
6 | body
7 | block content
8 |
9 | // GitHub ribbon! via https://github.com/blog/273-github-ribbons
10 | a(href='https://github.com/aseemk/node-neo4j-template')
11 | img(
12 | style='position: absolute; top: 0; right: 0; border: 0;'
13 | src='https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png'
14 | alt='Fork me on GitHub'
15 | )
--------------------------------------------------------------------------------
/views/users.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1 Users
5 |
6 | if users.length
7 | p Here are the current users:
8 | ul.users
9 | for user in users
10 | li.user
11 | a(href='/users/#{user.id}') #{user.name}
12 | else
13 | p There are no users currently.
14 |
15 | form(action='', method='POST')
16 | p Create a new user:
17 | input(type='text', name='name', placeholder='Name', required)
18 | input(type='submit', value='Create')
19 |
--------------------------------------------------------------------------------
/models/neo4j/pet.js:
--------------------------------------------------------------------------------
1 | // extracts just the data from the query results
2 |
3 | var _ = require('underscore');
4 |
5 | var Pet = module.exports = function (_node) {
6 | _(this).extend(_node.data);
7 | };
8 |
9 | Pet.prototype.owners = function (owners) {
10 | if (owners && owners.length) {
11 | this.owners = owners;
12 | }
13 | return this.owners;
14 | };
15 |
16 | Pet.prototype.category = function (category) {
17 | if (category) {
18 | if (category.name) {
19 | this.category = category;
20 | } else if (category.data) {
21 | this.category = _.extend(category.data);
22 | }
23 | }
24 | return this.category;
25 | };
--------------------------------------------------------------------------------
/views/index.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1= title
5 |
6 | p
7 | | This is a template app showing the use of
8 | a(href='http://www.neo4j.org/', target='_blank') Neo4j
9 | | from Node.js. It uses the
10 | a(href='https://github.com/thingdom/node-neo4j', target='_blank') node-neo4j
11 | | library, available on npm as
12 | code neo4j
13 | .
14 |
15 | p.
16 | This app is a simple social network manager: it lets you add and remove
17 | users and "follows" relationships between them.
18 |
19 | p
20 | strong
21 | | Get started:
22 | a(href='/users') View all users
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled source #
2 | ###################
3 | *.com
4 | *.class
5 | *.dll
6 | *.exe
7 | *.o
8 | *.so
9 |
10 | # Packages #
11 | ############
12 | # it's better to unpack these files and commit the raw source
13 | # git has its own built in compression methods
14 | *.7z
15 | *.dmg
16 | *.gz
17 | *.iso
18 | *.jar
19 | *.rar
20 | *.tar
21 | *.zip
22 |
23 | # Logs and databases #
24 | ######################
25 | *.log
26 | *.sql
27 | *.sqlite
28 |
29 | # OS generated files #
30 | ######################
31 | .DS_Store*
32 | ehthumbs.db
33 | Icon?
34 | Thumbs.db
35 | *.swp
36 | node_modules
37 | .env
38 | .env_local
39 | .env_hosted
40 | .env_staging
41 | .env_development
42 | .env_production
43 |
44 | #directories#
45 | node_modules/
46 |
47 | public/app/scripts/vendor/*/
48 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 - 2014 tinj
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "neo4j-swagger-api",
3 | "version": "0.1.4",
4 | "author": {
5 | "name": "Mat Tyndall",
6 | "email": "mat@tinj.com",
7 | "url": "https://github.com/flipside"
8 | },
9 | "description": "node neo4j api server using express and swagger",
10 | "repository": {
11 | "type": "git",
12 | "url": "https://github.com/tinj/node-neo4j-swagger-api"
13 | },
14 | "keywords": [
15 | "node",
16 | "neo4j",
17 | "express",
18 | "api",
19 | "http",
20 | "rest",
21 | "swagger",
22 | "server"
23 | ],
24 | "engines": {
25 | "node": "0.10.x",
26 | "npm": "1.2.x"
27 | },
28 | "dependencies": {
29 | "connect": "2.12.x",
30 | "express": "3.x",
31 | "jade": ">=0.26.3",
32 | "neo4j": ">=1.1.0",
33 | "underscore": ">=1.3.1",
34 | "underscore.string": ">=2.3.3",
35 | "swagger-node-express": "2.0.x",
36 | "neo4j-swagger-ui": ">=0.0.14",
37 | "neo4j-architect": ">=0.1.1",
38 | "colors": "0.6.x",
39 | "async": ">=0.2.9",
40 | "moment": ">=2.4.0",
41 | "hat": ">=0.0.3",
42 | "mocha": ">= 1.6.0",
43 | "should": ">= 0.6.3",
44 | "random-name": ">=0.1.0"
45 | },
46 | "license": "MIT"
47 | }
48 |
--------------------------------------------------------------------------------
/views/user.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1 #{user.name}
5 |
6 | p
7 | a(href='/users') Back to everyone
8 |
9 | if following.length
10 | p #{user.name} is following #{following.length} users:
11 | // TODO should say 'user' if only one! ;)
12 | ul.users
13 | for other in following
14 | li.user
15 | form(action='/users/#{user.id}/unfollow', method='POST')
16 | a(href='/users/#{other.id}') #{other.name}
17 | input(type='hidden', name='user[id]', value='#{other.id}')
18 | input(type='submit', class='unfollow', value='x')
19 | else
20 | p #{user.name} isn't following anyone currently.
21 |
22 | if others.length
23 | form(action='/users/#{user.id}/follow', method='POST')
24 | p Add someone for #{user.name} to follow:
25 | label
26 | select(name='user[id]', required)
27 | option(value='')
28 | for user in others
29 | option(value='#{user.id}') #{user.name}
30 | input(type='submit', value='Follow')
31 | else
32 | p There's no one else left for #{user.name} to follow!
33 |
34 | form(action='/users/#{user.id}', method='POST')
35 | p Edit this user:
36 | input(type='text', name='name', placeholder='#{user.name}', required)
37 | input(type='submit', value='Update')
38 |
39 | form(action='/users/#{user.id}', method='POST', onsubmit='return confirm("Are you sure?");')
40 | p And if you're feeling destructive…
41 | input(type='hidden', name='_method', value='DELETE')
42 | input(type='submit', value='Delete User')
--------------------------------------------------------------------------------
/models/swagger_models.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "Category":{
3 | "id":"Category",
4 | "properties":{
5 | "id":{
6 | "type":"string"
7 | },
8 | "name":{
9 | "type":"string"
10 | }
11 | }
12 | },
13 | "Count":{
14 | "id":"Count",
15 | "properties": {
16 | "count":{
17 | "type":"integer"
18 | }
19 | }
20 | },
21 | "Pet":{
22 | "id":"Pet",
23 | "properties":{
24 | // "tags":{
25 | // "items":{
26 | // "$ref":"Tag"
27 | // },
28 | // "type":"Array"
29 | // },
30 | "id":{
31 | "type":"string"
32 | },
33 | // "category":{
34 | // "items": {
35 | // "$ref":"Category"
36 | // }
37 | // },
38 | // "status":{
39 | // "allowableValues":{
40 | // "valueType":"LIST",
41 | // "values":[
42 | // "available",
43 | // "pending",
44 | // "sold"
45 | // ],
46 | // "valueType":"LIST"
47 | // },
48 | // "description":"pet status in the store",
49 | // "type":"string"
50 | // },
51 | "name":{
52 | "type":"string"
53 | }
54 | // "photoUrls":{
55 | // "items":{
56 | // "type":"string"
57 | // },
58 | // "type":"Array"
59 | // }
60 | }
61 | },
62 | "User":{
63 | "id":"User",
64 | // "required": ["id"],
65 | "properties":{
66 | "id":{
67 | "type":"string",
68 | "description": "UUID"
69 | },
70 | "name":{
71 | "type":"string",
72 | "description": "Name of User"
73 | },
74 | "created":{
75 | "type":"integer",
76 | "description":"Unix Time Created"
77 | }
78 | }
79 | },
80 | "newUser":{
81 | "id":"newUser",
82 | "required": ["name"],
83 | "properties":{
84 | "name":{
85 | "type":"string",
86 | }
87 | }
88 | },
89 | "Tag":{
90 | "id":"Tag",
91 | "properties":{
92 | "id":{
93 | "type":"long"
94 | },
95 | "name":{
96 | "type":"string"
97 | }
98 | }
99 | }
100 | };
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | node-neo4j-swagger-api
2 | =====================
3 |
4 | This is an open source node neo4j api server based on `node-neo4j-template` and `swagger-node-express`
5 |
6 | The idea is to make it as easy as possible to create an API using Node.js and Neo4j that can be consumed by some other app. Swagger provides interactive documentation so that it is easy to interact with the API. The goal is merge Swagger with Neo4j queries and visualizations so developers can see how Neo4j and the API results relate to each other.
7 |
8 |
9 | Try it out at [neo4j-swagger](http://neo4j-swagger.tinj.com/docs)
10 |
11 | ### Resources
12 | - [Neo4j-Swagger UI](https://github.com/tinj/neo4j-swagger-ui) - neo4j-swagger client
13 | - [Neo4j-Architect](https://github.com/tinj/neo4j-architect) - neo4j query builder
14 | - [Node-Neo4j](https://github.com/thingdom/node-neo4j) - neo4j client library
15 | - [Neo4j](http://www.neo4j.org) - Main Neo4j site.
16 | - [Graphene DB](http://www.graphenedb.com) - Neo4j Cloud Host
17 | - [node-neo4j-template](https://github.com/thingdom/node-neo4j) - How I got started with neo4j and node
18 | - [Swagger-Node-Express](https://github.com/wordnik/swagger-node-express) - Merges swagger and express
19 | - [Swagger](https://developers.helloreverb.com/swagger/) - Learn more about Swagger
20 | - [graphgist](https://github.com/neo4j-contrib/graphgist/) - gists made for neo4j with awesome visualizations
21 |
22 |
23 | _Built focusing on using Cypher and Neo4j 2.0_
24 |
25 | [Neo4j-Swagger Diagram](/views/img/neo4j-swagger.jpg "Neo4j-Swagger Diagram")
26 |
27 | ### Prerequisites
28 | - Neo4j
29 | - Node.js
30 |
31 | ### Getting Started
32 |
33 | - clone `git clone https://github.com/tinj/node-neo4j-swagger-api.git`
34 | - `npm install`
35 | - have a neo4j server up at [localhost:7474](http://localhost:3474) or `NEO4J_URL` specified in .env
36 | - `node app.js`
37 | - visit [http://localhost:3000/docs](http://localhost:3000/docs)
38 |
39 |
40 | ### TODO
41 |
42 | I'm using [Trello](https://trello.com/b/kelJzC12/neo4j-swagger) for my todo list, feel free to comment!
43 |
44 | 1. Move neo4j queries and results to swagger client
45 | 2. Add graph visualization to swagger client
46 | 3. improve swagger model format
47 | 4. add second node type (pets?)
48 | 5. add queries relating users and pets
49 | 6. TESTS!!!
50 | 7. ...
51 |
52 |
53 | ### More details about goals
54 | So basically, Swagger creates interactive API docs, which is great but I also want to see what's going on with Neo4j at the same time
55 |
56 | The idea is to pass raw query/results optionally in API responses which could be visualized on the Swagger client along side the API results
57 |
58 | end result being i can see what Neo4j and my API are doing side by side in the web client so I can stop debugging in the console
59 |
60 | _MIT License_
61 |
--------------------------------------------------------------------------------
/models/oldpets.js:
--------------------------------------------------------------------------------
1 | var tags = {
2 | 1: {id: 1, name: "tag1"},
3 | 2: {id: 2, name: "tag2"},
4 | 3: {id: 3, name: "tag3"},
5 | 4: {id: 4, name: "tag4"}};
6 |
7 | var categories = {
8 | 1: {id: 1, name: "Dogs"},
9 | 2: {id: 2, name: "Cats"},
10 | 3: {id: 3, name: "Rabbits"},
11 | 4: {id: 4, name: "Lions"}};
12 |
13 | var pets = {
14 | 1: {id: 1,
15 | category: categories[2],
16 | name: "Cat 1",
17 | urls: ["url1", "url2"],
18 | tags: [tags[1], tags[2]],
19 | status: "available"},
20 | 2: {id: 2,
21 | category: categories[2],
22 | name: "Cat 2",
23 | urls: ["url1", "url2"],
24 | tags: [tags[2], tags[3]],
25 | status: "available"},
26 | 3: {id: 3,
27 | category: categories[2],
28 | name: "Cat 3",
29 | urls: ["url1", "url2"],
30 | tags: [tags[3], tags[4]],
31 | status: "available"},
32 | 4: {id: 4,
33 | category: categories[1],
34 | name: "Dog 1",
35 | urls: ["url1", "url2"],
36 | tags: [tags[1], tags[2]],
37 | status: "available"},
38 | 5: {id: 5,
39 | category: categories[1],
40 | name: "Dog 2",
41 | urls: ["url1", "url2"],
42 | tags: [tags[2], tags[3]],
43 | status: "available"},
44 | 6: {id: 6,
45 | category: categories[1],
46 | name: "Dog 3",
47 | urls: ["url1", "url2"],
48 | tags: [tags[3], tags[4]],
49 | status: "available"},
50 | 7: {id: 7,
51 | category: categories[4],
52 | name: "Lion 1",
53 | urls: ["url1", "url2"],
54 | tags: [tags[1], tags[2]],
55 | status: "available"},
56 | 8: {id: 8,
57 | category: categories[4],
58 | name: "Lion 2",
59 | urls: ["url1", "url2"],
60 | tags: [tags[2], tags[3]],
61 | status: "available"},
62 | 9: {id: 9,
63 | category: categories[4],
64 | name: "Lion 3",
65 | urls: ["url1", "url2"],
66 | tags: [tags[3], tags[4]],
67 | status: "available"},
68 | 10: {id: 10,
69 | category: categories[3],
70 | name: "Rabbit 1",
71 | urls: ["url1", "url2"],
72 | tags: [tags[3], tags[4]],
73 | status: "available"}
74 | };
75 |
76 |
77 | exports.getPetById = function getPetById(id) {
78 | return pets[id];
79 | };
80 |
81 |
82 | exports.findPetByStatus = function findPetByStatus(status) {
83 | var keys = {}
84 | var array = status.split(",");
85 | array.forEach(function(item) {
86 | keys[item] = item;
87 | });
88 | var output = [];
89 | for(var key in pets) {
90 | var pet = pets[key];
91 | if(pet.status && keys[pet.status]) output.push(pet);
92 | }
93 | return output;
94 | };
95 |
96 | exports.findPetByTags = function findPetByTags(tags) {
97 | var keys = {};
98 | var array = tags.split(",");
99 | array.forEach(function(item) {
100 | keys[item] = item;
101 | });
102 | var output = [];
103 | for(var key in pets) {
104 | var pet = pets[key];
105 | if(pet.tags) {
106 | pet.tags.forEach(function (tag) {
107 | if(tag.name && keys[tag.name]) output.push(pet);
108 | });
109 | }
110 | }
111 | return output;
112 | };
113 |
114 | exports.addPet = function addPet(pet){
115 | pets[pet.id] = pet;
116 | };
117 |
118 | exports.deletePet = function deletePet(id) {
119 | delete pets[id];
120 | };
--------------------------------------------------------------------------------
/routes/pets.js:
--------------------------------------------------------------------------------
1 | // this still uses the example code from the swagger petstore example
2 |
3 | var Pets = require('../models/pets');
4 |
5 | var sw = require("swagger-node-express");
6 | var param = sw.params;
7 | var url = require("url");
8 | var swe = sw.errors;
9 |
10 | var petData = require("../models/oldpets");
11 |
12 | function writeResponse (res, data) {
13 | sw.setHeaders(res);
14 | res.send(JSON.stringify(data));
15 | }
16 |
17 | exports.findById = {
18 | 'spec': {
19 | "description" : "Operations about pets",
20 | "path" : "/pet/{petId}",
21 | "notes" : "Returns a pet based on ID",
22 | "summary" : "Find pet by ID",
23 | "method": "GET",
24 | "params" : [param.path("petId", "ID of pet that needs to be fetched", "string")],
25 | "responseClass" : "Pet",
26 | "errorResponses" : [swe.invalid('id'), swe.notFound('pet')],
27 | "nickname" : "getPetById"
28 | },
29 | 'action': function (req,res) {
30 | if (!req.params.petId) {
31 | throw swe.invalid('id'); }
32 | var id = parseInt(req.params.petId);
33 | var pet = petData.getPetById(id);
34 |
35 | if(pet) res.send(JSON.stringify(pet));
36 | else throw swe.notFound('pet');
37 | }
38 | };
39 |
40 | exports.findByStatus = {
41 | 'spec': {
42 | "description" : "Operations about pets",
43 | "path" : "/pet/findByStatus",
44 | "notes" : "Multiple status values can be provided with comma-separated strings",
45 | "summary" : "Find pets by status",
46 | "method": "GET",
47 | "params" : [
48 | param.query("status", "Status in the store", "string", true, true, "LIST[available,pending,sold]", "available")
49 | ],
50 | "responseClass" : "List[Pet]",
51 | "errorResponses" : [swe.invalid('status')],
52 | "nickname" : "findPetsByStatus"
53 | },
54 | 'action': function (req,res) {
55 | var statusString = url.parse(req.url,true).query["status"];
56 | if (!statusString) {
57 | throw swe.invalid('status'); }
58 |
59 | var output = petData.findPetByStatus(statusString);
60 | res.send(JSON.stringify(output));
61 | }
62 | };
63 |
64 | exports.findByTags = {
65 | 'spec': {
66 | "path" : "/pet/findByTags",
67 | "notes" : "Multiple tags can be provided with comma-separated strings. Use tag1, tag2, tag3 for testing.",
68 | "summary" : "Find pets by tags",
69 | "method": "GET",
70 | "params" : [param.query("tags", "Tags to filter by", "string", true, true)],
71 | "responseClass" : "List[Pet]",
72 | "errorResponses" : [swe.invalid('tag')],
73 | "nickname" : "findPetsByTags"
74 | },
75 | 'action': function (req,res) {
76 | var tagsString = url.parse(req.url,true).query["tags"];
77 | if (!tagsString) {
78 | throw swe.invalid('tag'); }
79 | var output = petData.findPetByTags(tagsString);
80 | writeResponse(res, output);
81 | }
82 | };
83 |
84 | exports.addPet = {
85 | 'spec': {
86 | "path" : "/pets",
87 | "notes" : "adds a pet to the graph with a category",
88 | "summary" : "Add a new pet to the graph with a category",
89 | "method": "POST",
90 | "responseClass" : "Pet",
91 | "params" : [param.body("Pet", "Pet name", "Pet")],
92 | "errorResponses" : [swe.invalid('input')],
93 | "nickname" : "addPet"
94 | },
95 | 'action': function(req, res) {
96 | var body = req.body || {};
97 |
98 | var name = body.name;
99 | var category = body.category;
100 | console.log(name, category);
101 | if (!name){
102 | throw swe.invalid('name');
103 | } else if (!category) {
104 | throw swe.invalid('category');
105 | } else {
106 | Pets.create({
107 | name: name,
108 | category: category
109 | }, {}, function (err, pet) {
110 | console.log(err);
111 | console.log(pet);
112 | if (err || !pet) throw swe.invalid('input');
113 | res.send(JSON.stringify(pet));
114 | });
115 | }
116 | }
117 | };
118 |
119 |
120 | exports.updatePet = {
121 | 'spec': {
122 | "path" : "/pet",
123 | "notes" : "updates a pet in the store",
124 | "method": "PUT",
125 | "summary" : "Update an existing pet",
126 | "params" : [param.body("Pet", "Pet object that needs to be updated in the store", "Pet")],
127 | "errorResponses" : [swe.invalid('id'), swe.notFound('pet'), swe.invalid('input')],
128 | "nickname" : "addPet"
129 | },
130 | 'action': function(req, res) {
131 | var body = req.body;
132 | if(!body || !body.id){
133 | throw swe.invalid('pet');
134 | }
135 | else {
136 | petData.addPet(body);
137 | res.send(200);
138 | }
139 | }
140 | };
141 |
142 | exports.deletePet = {
143 | 'spec': {
144 | "path" : "/pet/{id}",
145 | "notes" : "removes a pet from the store",
146 | "method": "DELETE",
147 | "summary" : "Remove an existing pet",
148 | "params" : [param.path("id", "ID of pet that needs to be removed", "string")],
149 | "errorResponses" : [swe.invalid('id'), swe.notFound('pet')],
150 | "nickname" : "deletePet"
151 | },
152 | 'action': function(req, res) {
153 | var id = parseInt(req.params.id);
154 | petData.deletePet(id);
155 | res.send(200);
156 | }
157 | };
--------------------------------------------------------------------------------
/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Module dependencies.
3 | */
4 |
5 | var express = require('express')
6 | , url = require("url")
7 | , swagger = require("swagger-node-express")
8 | // , http = require('http')
9 | // , path = require('path')
10 |
11 | , routes = require('./routes')
12 |
13 | , PORT = process.env.PORT || 3000
14 | , API_STRING = '/api/v0'
15 | , BASE_URL = process.env.BASE_URL || process.env.BASE_CALLBACK_URL || "http://localhost:"+PORT
16 |
17 | , app = express()
18 | , subpath = express();
19 |
20 |
21 | app.use(API_STRING, subpath);
22 |
23 | // configure /api/v0 subpath for api versioning
24 | subpath.configure(function () {
25 | // just using json for the api
26 | subpath.use(express.json());
27 |
28 | subpath.use(express.methodOverride());
29 | });
30 |
31 | app.configure(function () {
32 | // all environments
33 | app.set('port', PORT);
34 | // app.set('views', __dirname + '/views');
35 | // app.set('view engine', 'jade');
36 | app.use(express.favicon());
37 | // app.use(express.static(path.join(__dirname, 'public')));
38 | app.use(express.logger('dev'));
39 |
40 | // just using json for the api
41 | app.use(express.json());
42 |
43 | app.use(express.methodOverride());
44 | app.use(app.router);
45 |
46 |
47 | // development only
48 | if ('development' == app.get('env')) {
49 | app.use(express.errorHandler());
50 | }
51 |
52 | // app.locals({
53 | // title: 'node-neo4j-swagger-api' // default title
54 | // });
55 | });
56 |
57 |
58 | // Set the main handler in swagger to the express subpath
59 | swagger.setAppHandler(subpath);
60 |
61 | // This is a sample validator. It simply says that for _all_ POST, DELETE, PUT
62 | // methods, the header `api_key` OR query param `api_key` must be equal
63 | // to the string literal `special-key`. All other HTTP ops are A-OK
64 | swagger.addValidator(
65 | function validate(req, path, httpMethod) {
66 | // example, only allow POST for api_key="special-key"
67 | if ("POST" == httpMethod || "DELETE" == httpMethod || "PUT" == httpMethod) {
68 | var apiKey = req.headers["api_key"];
69 | if (!apiKey) {
70 | apiKey = url.parse(req.url,true).query["api_key"]; }
71 | if ("special-key" == apiKey) {
72 | return true;
73 | }
74 | return false;
75 | }
76 | return true;
77 | }
78 | );
79 |
80 |
81 | var models = require("./models/swagger_models");
82 |
83 | // Add models and methods to swagger
84 | swagger.addModels(models)
85 | .addGet(routes.users.list)
86 | .addGet(routes.users.userCount)
87 | .addGet(routes.users.findById)
88 | .addGet(routes.users.getRandom)
89 | .addPost(routes.users.addUser)
90 | .addPost(routes.users.addRandomUsers)
91 | .addPost(routes.users.manyRandomFriendships)
92 | .addPost(routes.users.friendRandomUser)
93 | .addPost(routes.users.friendUser)
94 | .addPost(routes.users.unfriendUser)
95 | .addPut(routes.users.updateUser)
96 | .addDelete(routes.users.deleteUser)
97 | .addDelete(routes.users.deleteAllUsers)
98 | .addPut(routes.users.resetUsers)
99 |
100 | // .addGet(routes.pets.findByTags)
101 | // .addGet(routes.pets.findByStatus)
102 | // .addGet(routes.pets.findById)
103 | // .addPost(routes.pets.addPet)
104 | // .addPut(routes.pets.updatePet)
105 | // .addDelete(routes.pets.deletePet)
106 | ;
107 |
108 |
109 | // Configures the app's base path and api version.
110 | console.log(BASE_URL+API_STRING);
111 |
112 | swagger.configureDeclaration("users", {
113 | description: "User Operations",
114 | // authorizations: ["oath2"],
115 | produces: ["application/json"]
116 | });
117 |
118 | // set api info
119 | swagger.setApiInfo({
120 | title: "Neo4j-Swagger API",
121 | description: "This a sample server built on top of Neo4j, a graph database. The neo4j toggle (top right) controls whether the underlying neo4j cypher queries are returned to the client. Learn more at https://github.com/tinj/node-neo4j-swagger-api TODO: Add graph visualizations! (help wanted)",
122 | contact: "mat@tinj.com"
123 | });
124 |
125 | swagger.setAuthorizations({
126 | apiKey: {
127 | type: "apiKey",
128 | passAs: "header"
129 | }
130 | });
131 |
132 | swagger.configureSwaggerPaths("", "api-docs", "");
133 |
134 | swagger.configure(BASE_URL+API_STRING, "0.1.4");
135 |
136 |
137 | // Routes
138 |
139 | // Serve up swagger ui at /docs via static route
140 | var docs_handler = express.static(__dirname + '/node_modules/neo4j-swagger-ui/dist/');
141 | app.get(/^\/docs(\/.*)?$/, function(req, res, next) {
142 | if (req.url === '/docs') { // express static barfs on root url w/o trailing slash
143 | res.writeHead(302, { 'Location' : req.url + '/' });
144 | res.end();
145 | return;
146 | }
147 | // take off leading /docs so that connect locates file correctly
148 | req.url = req.url.substr('/docs'.length);
149 | return docs_handler(req, res, next);
150 | });
151 |
152 | // redirect to /docs
153 | app.get('/', function(req, res) {
154 | res.redirect('./docs');
155 | });
156 |
157 | // app.get('/users', routes.users.list);
158 | // app.post('/users', routes.users.create);
159 | // app.get('/users/:id', routes.users.show);
160 | // app.post('/users/:id', routes.users.edit);
161 | // app.del('/users/:id', routes.users.del);
162 |
163 | // app.post('/users/:id/follow', routes.users.follow);
164 | // app.post('/users/:id/unfollow', routes.users.unfollow);
165 |
166 |
167 | app.listen(app.get('port'), function() {
168 | console.log('Express server listening on port ' + app.get('port'));
169 | });
--------------------------------------------------------------------------------
/models/pets.js:
--------------------------------------------------------------------------------
1 | // needs to be completely rewritten based on user.js
2 |
3 | // /**
4 | // * neo4j pet functions
5 | // * these are mostly written in a functional style
6 | // */
7 |
8 |
9 | // var _ = require('underscore');
10 | // var _s = require('underscore.string');
11 | // var uuid = require('hat');
12 | // var Cypher = require('../neo4j/cypher');
13 | // var Pet = require('../models/neo4j/pet');
14 | // var Category = require('../models/neo4j/category');
15 |
16 |
17 | // /**
18 | // * Result Functions
19 | // * to be combined with queries using _.partial()
20 | // */
21 |
22 | // // return a single pet
23 | // var _singlePet = function (results, callback) {
24 | // callback(null, new Pet(results[0].pet));
25 | // };
26 |
27 | // // return a single pet with a category
28 | // var _singlePetWithCategory = function (results, callback) {
29 | // var category = new Category(results[0].category);
30 | // var pet = new Pet(results[0].pet);
31 | // pet.category(category);
32 | // callback(null, pet);
33 | // };
34 |
35 | // // return many pets
36 | // var _manyPets = function (results, callback) {
37 | // var pets = _.map(results, function (result) {
38 | // return new Pet(result.pet);
39 | // });
40 |
41 | // callback(null, pets);
42 | // };
43 |
44 | // // returns a pet and a owner
45 | // var _singlePetWithOwner = function (results, callback) {
46 | // callback(null, new Pet(results[0].pet), new Pet(results[0].owner));
47 | // };
48 |
49 | // // returns a pet and their owners from a cypher result
50 | // var _parsePetWithOwners = function (result) {
51 | // var pet = new Pet(result.pet);
52 | // var owners = _.map(result.owners, function (owner) {
53 | // return new Pet(owner);
54 | // });
55 | // pet.owners(owners);
56 | // return pet;
57 | // };
58 |
59 | // // returns a pet and their owners
60 | // var _singlePetWithOwners = function (results, callback) {
61 | // callback(null, _parsePetWithOwners(results[0]));
62 | // };
63 |
64 | // // returns many pets and their owners
65 | // var _manyPetsWithOwners = function (results, callback) {
66 | // var pets = _.map(results, _parsePetWithOwners);
67 | // callback(null, pets);
68 | // };
69 |
70 | // /**
71 | // * Query Functions
72 | // * to be combined with result functions using _.partial()
73 | // */
74 |
75 |
76 | // var _matchBy = function (keys, params, options, callback) {
77 | // var cypher_params = _.pick(params, keys);
78 |
79 | // var query = [
80 | // 'MATCH (pet:Pet)',
81 | // Cypher.where('pet', keys),
82 | // 'RETURN pet'
83 | // ].join('\n');
84 |
85 | // callback(null, query, cypher_params);
86 | // };
87 |
88 | // var _matchByUUID = _.partial(_matchBy, ['uuid']);
89 | // var _matchByName = _.partial(_matchBy, ['name']);
90 | // var _matchAll = _.partial(_matchBy, []);
91 |
92 | // var _matchByCategory = function (keys, params, options, callback) {
93 | // var cypher_params = _.pick(params, keys);
94 |
95 | // var query = [
96 | // 'MATCH (pet:Pet)-[:is_a]->(category:Category)',
97 | // Cypher.where('pet', keys),
98 | // 'RETURN pet'
99 | // ].join('\n');
100 |
101 | // callback(null, query, cypher_params);
102 | // };
103 |
104 | // var _updateName = function (params, options, callback) {
105 | // var cypher_params = {
106 | // uuid : params.uuid,
107 | // name : params.name
108 | // };
109 |
110 | // var query = [
111 | // 'MATCH (pet:Pet)',
112 | // 'WHERE pet.uuid = {uuid}',
113 | // 'SET pet.name = {name}',
114 | // 'RETURN pet'
115 | // ].join('\n');
116 |
117 | // callback(null, query, cypher_params);
118 | // };
119 |
120 | // // creates the pet with cypher
121 | // var _create = function (params, options, callback) {
122 | // var cypher_params = {
123 | // uuid: params.uuid || uuid(),
124 | // name: params.name,
125 | // category: _s.capitalize(params.category)
126 | // };
127 |
128 | // var query = [
129 | // 'MERGE (pet:Pet {name: {name}, uuid: {uuid}})',
130 | // 'ON CREATE pet',
131 | // 'SET pet.created = timestamp()',
132 | // 'WITH pet',
133 | // 'MERGE (category:Category {name: {category}})',
134 | // 'ON CREATE category',
135 | // 'SET category.created = timestamp()',
136 | // 'WITH pet, category',
137 | // 'CREATE UNIQUE (pet)-[:is_a]->(category)',
138 | // 'RETURN pet, category'
139 | // ].join('\n');
140 | // console.log(query);
141 | // callback(null, query, cypher_params);
142 | // };
143 |
144 | // // delete the pet and any relationships with cypher
145 | // var _delete = function (params, options, callback) {
146 | // var cypher_params = {
147 | // uuid: params.uuid
148 | // };
149 |
150 | // var query = [
151 | // 'MATCH (pet:Pet)',
152 | // 'WHERE pet.uuid={uuid}',
153 | // 'WITH pet',
154 | // 'MATCH (pet)-[r?]-()',
155 | // 'DELETE pet, r',
156 | // ].join('\n');
157 | // callback(null, query, cypher_params);
158 | // };
159 |
160 |
161 | // // owner the pet
162 | // var _owner = function (params, options, callback) {
163 | // var cypher_params = {
164 | // uuid: params.uuid,
165 | // owner: params.owner
166 | // };
167 |
168 | // var query = [
169 | // 'MATCH (pet:Pet), (owner:Pet)',
170 | // 'WHERE pet.uuid={uuid} AND owner.uuid={owner} AND NOT((pet)-[:owner]-(owner))',
171 | // 'CREATE (pet)-[:owner {created: timestamp()}]->(owner)',
172 | // 'RETURN pet, owner'
173 | // ].join('\n');
174 | // callback(null, query, cypher_params);
175 | // };
176 |
177 | // // unowner the pet
178 | // var _unowner = function (params, options, callback) {
179 | // var cypher_params = {
180 | // uuid: params.uuid,
181 | // owner: params.owner
182 | // };
183 |
184 | // var query = [
185 | // 'MATCH (pet:Pet)-[r:owner]-(owner:Pet)',
186 | // 'WHERE pet.uuid={uuid} AND owner.uuid={owner}',
187 | // 'DELETE r',
188 | // 'RETURN pet, owner'
189 | // ].join('\n');
190 | // callback(null, query, cypher_params);
191 | // };
192 |
193 | // // match with owners
194 | // var _matchWithOwners = function (params, options, callback) {
195 | // var cypher_params = {
196 | // uuid: params.uuid
197 | // };
198 |
199 | // var query = [
200 | // 'MATCH (pet:Pet)',
201 | // 'WHERE pet.uuid={uuid}',
202 | // 'WITH pet',
203 | // 'MATCH (pet)-[r?:owner]-(owner:Pet)',
204 | // 'RETURN pet, COLLECT(owner) as owners'
205 | // ].join('\n');
206 | // callback(null, query, cypher_params);
207 | // };
208 |
209 |
210 | // // match all with owners
211 | // var _matchAllWithOwners = function (params, options, callback) {
212 | // var cypher_params = {};
213 |
214 | // var query = [
215 | // 'MATCH (pet:Pet)',
216 | // 'WITH pet',
217 | // 'MATCH (pet)-[r?:owner]-(owner:Pet)',
218 | // 'RETURN pet, COLLECT(owner) as owners'
219 | // ].join('\n');
220 | // callback(null, query, cypher_params);
221 | // };
222 |
223 |
224 |
225 | // // exposed functions
226 |
227 | // module.exports = {
228 | // // get a single pet by uuid
229 | // getByUUID: Cypher(_matchByUUID, _singlePet),
230 |
231 | // // get a single pet by name
232 | // getByName: Cypher(_matchByName, _singlePet),
233 |
234 | // // get a pet by uuid and update their name
235 | // updateName: Cypher(_updateName, _singlePet),
236 |
237 | // // create a new pet
238 | // create: Cypher(_create, _singlePetWithCategory),
239 |
240 | // // login a pet
241 | // login: Cypher(_create, _singlePet),
242 |
243 | // // get all pets
244 | // getAll: Cypher(_matchAll, _manyPets),
245 |
246 | // // owner a pet by uuid
247 | // ownerPet: Cypher(_owner, _singlePetWithOwner),
248 |
249 | // // unowner a pet by uuid
250 | // unownerPet: Cypher(_unowner, _singlePetWithOwner),
251 |
252 | // // delete a pet by uuid
253 | // deletePet: Cypher(_delete),
254 |
255 | // // get a single pet by uuid and all owners
256 | // getWithOwners: Cypher(_matchWithOwners, _singlePetWithOwners),
257 |
258 | // // get all pets and all owners
259 | // getAllWithOwners: Cypher(_matchAllWithOwners, _manyPetsWithOwners)
260 | // };
--------------------------------------------------------------------------------
/models/users.js:
--------------------------------------------------------------------------------
1 | /**
2 | * neo4j user functions
3 | * these are mostly written in a functional style
4 | */
5 |
6 |
7 | var _ = require('underscore');
8 | var uuid = require('hat'); // generates uuids
9 | var Architect = require('neo4j-architect');
10 | Architect.init();
11 | var Construct = Architect.Construct;
12 | var Cypher = Architect.Cypher;
13 | var User = require('../models/neo4j/user');
14 | // var async = require('async');
15 | var randomName = require('random-name');
16 |
17 |
18 | /*
19 | * Utility Functions
20 | */
21 |
22 | function _randomName () {
23 | return randomName.first() + ' ' + randomName.last();
24 | }
25 |
26 | function _randomNames (n) {
27 | return _.times(n, _randomName);
28 | }
29 |
30 | /**
31 | * Result Functions
32 | * to be combined with queries using _.partial()
33 | */
34 |
35 | // return a single user
36 | var _singleUser = function (results, callback) {
37 | if (results.length) {
38 | callback(null, new User(results[0].user));
39 | } else {
40 | callback(null, null);
41 | }
42 | };
43 |
44 | // return many users
45 | var _manyUsers = function (results, callback) {
46 | var users = _.map(results, function (result) {
47 | return new User(result.user);
48 | });
49 |
50 | callback(null, users);
51 | };
52 |
53 | // returns a user and a friend
54 | var _singleUserWithFriend = function (results, callback) {
55 | if (!results.length) return callback();
56 | callback(null, {
57 | user: new User(results[0].user),
58 | friend: new User(results[0].friend)
59 | });
60 | };
61 |
62 | // returns a user and their friends from a cypher result
63 | var _parseUserWithFriends = function (result) {
64 | var user = new User(result.user);
65 | var friends = _.map(result.friends, function (friend) {
66 | return new User(friend);
67 | });
68 | user.friends(friends);
69 | return user;
70 | };
71 |
72 | // returns a user and their friends
73 | var _singleUserWithFriends = function (results, callback) {
74 | if (!results.length) return callback();
75 | callback(null, _parseUserWithFriends(results[0]));
76 | };
77 |
78 | // returns a user and their friends and friends of friends
79 | var _singleUserWithFriendsAndFOF = function (results, callback) {
80 | if (!results.length) return callback();
81 |
82 | var user = new User(results[0].user);
83 | user.friends = _.chain(results).map(function (result) {
84 | if (result.friend) {
85 | var friend = new User(result.friend);
86 | friend.friends = _.map(result.fofs, function (fof) {
87 | return new User(fof);
88 | });
89 | return friend;
90 | }
91 | }).compact().value();
92 | callback(null, user);
93 | };
94 |
95 | // returns a user and their friends of friends
96 | var _singleUserWithFOF = function (results, callback) {
97 | if (!results.length) return callback();
98 |
99 | var user = new User(results[0].user);
100 | user.fof = _.map(results[0].fofs, function (fof) {
101 | return new User(fof);
102 | });
103 | callback(null, user);
104 | };
105 |
106 | // returns many users and their friends
107 | var _manyUsersWithFriends = function (results, callback) {
108 | var users = _.map(results, _parseUserWithFriends);
109 | callback(null, users);
110 | };
111 |
112 | // return a count
113 | var _singleCount = function (results, callback) {
114 | if (results.length) {
115 | callback(null, {
116 | count: results[0].c || 0
117 | });
118 | } else {
119 | callback(null, null);
120 | }
121 | };
122 |
123 |
124 | /**
125 | * Query Functions
126 | * to be combined with result functions using _.partial()
127 | */
128 |
129 |
130 | var _matchBy = function (keys, params, callback) {
131 | var cypher_params = _.pick(params, keys);
132 |
133 | var query = [
134 | 'MATCH (user:User)',
135 | Cypher.where('user', keys),
136 | 'RETURN user'
137 | ].join('\n');
138 |
139 | callback(null, query, cypher_params);
140 | };
141 |
142 | var _matchByUUID = _.partial(_matchBy, ['id']);
143 | var _matchByName = _.partial(_matchBy, ['name']);
144 | var _matchAll = _.partial(_matchBy, []);
145 |
146 | // gets n random users
147 | var _getRandom = function (params, callback) {
148 | var cypher_params = {
149 | n: parseInt(params.n || 1)
150 | };
151 |
152 | var query = [
153 | 'MATCH (user:User)',
154 | 'RETURN user, rand() as rnd',
155 | 'ORDER BY rnd',
156 | 'LIMIT {n}'
157 | ].join('\n');
158 |
159 | callback(null, query, cypher_params);
160 | };
161 |
162 | // gets n random users with friends
163 | var _getRandomWithFriends = function (params, callback) {
164 | var cypher_params = {
165 | n: parseInt(params.n || 1)
166 | };
167 |
168 | var query = [
169 | 'MATCH (user:User)',
170 | 'WITH user, rand() as rnd',
171 | 'ORDER BY rnd',
172 | 'LIMIT {n}',
173 | 'OPTIONAL MATCH (user)-[r:friend]-(friend:User)',
174 | 'RETURN user, COLLECT(friend) as friends'
175 | ].join('\n');
176 |
177 | callback(null, query, cypher_params);
178 | };
179 |
180 |
181 | var _getAllCount = function (params, callback) {
182 | var cypher_params = {};
183 |
184 | var query = [
185 | 'MATCH (user:User)',
186 | 'RETURN COUNT(user) as c'
187 | ].join('\n');
188 |
189 | callback(null, query, cypher_params);
190 | };
191 |
192 | var _updateName = function (params, callback) {
193 | var cypher_params = {
194 | id : params.id,
195 | name : params.name
196 | };
197 |
198 | var query = [
199 | 'MATCH (user:User {id:{id}})',
200 | 'SET user.name = {name}',
201 | 'RETURN user'
202 | ].join('\n');
203 |
204 | callback(null, query, cypher_params);
205 | };
206 |
207 | // creates the user with cypher
208 | var _create = function (params, callback) {
209 | var cypher_params = {
210 | id: params.id || uuid(),
211 | name: params.name
212 | };
213 |
214 | var query = [
215 | 'MERGE (user:User {name: {name}, id: {id}})',
216 | 'ON CREATE',
217 | 'SET user.created = timestamp()',
218 | 'ON MATCH',
219 | 'SET user.lastLogin = timestamp()',
220 | 'RETURN user'
221 | ].join('\n');
222 |
223 | callback(null, query, cypher_params);
224 | };
225 |
226 | // creates many users with cypher
227 | // var _createMany = function (params, callback) {
228 | // var users = _.map(params.users, function (user) {
229 | // return {
230 | // id: user.id || uuid(),
231 | // name: user.name
232 | // };
233 | // });
234 |
235 | // var cypher_params = {
236 | // users: users
237 | // };
238 |
239 | // var query = [
240 | // 'MERGE (user:User {name: {users}.name, id: {users}.id})',
241 | // 'ON CREATE',
242 | // 'SET user.created = timestamp()',
243 | // 'ON MATCH',
244 | // 'SET user.lastLogin = timestamp()',
245 | // 'RETURN user'
246 | // ].join('\n');
247 |
248 | // callback(null, query, cypher_params);
249 | // };
250 |
251 | // delete the user and any relationships with cypher
252 | var _delete = function (params, callback) {
253 | var cypher_params = {
254 | id: params.id
255 | };
256 |
257 | var query = [
258 | 'MATCH (user:User {id:{id}})',
259 | 'OPTIONAL MATCH (user)-[r]-()',
260 | 'DELETE user, r',
261 | ].join('\n');
262 |
263 | // add confirmation?
264 |
265 | callback(null, query, cypher_params);
266 | };
267 |
268 | // var __delete = new Cypher()
269 | // .params('id')
270 | // .match({label: 'User', name: 'user', props: 'id'})
271 | // .optional('(user)-[r]-()')
272 | // .delete(['user','r']);
273 |
274 | // var ___delete = Cypher.deleteNode({label: 'User', name: 'user', params: 'id'});
275 |
276 | // delete all users
277 | var _deleteAll = function (params, callback) {
278 | var cypher_params = {};
279 |
280 | var query = [
281 | 'MATCH (user:User)',
282 | 'OPTIONAL MATCH (user)-[r]-()',
283 | 'DELETE user, r',
284 | ].join('\n');
285 | callback(null, query, cypher_params);
286 | };
287 |
288 |
289 | // friend the user
290 | var _friend = function (params, callback) {
291 | var cypher_params = {
292 | id: params.id,
293 | friend_id: params.friend_id
294 | };
295 |
296 | var query = [
297 | 'MATCH (user:User {id:{id}}), (friend:User {id:{friend_id}})',
298 | 'WHERE NOT((user)-[:friend]-(friend)) AND NOT(user = friend)',
299 | 'CREATE (user)-[:friend {created: timestamp()}]->(friend)',
300 | 'RETURN user, friend'
301 | ].join('\n');
302 | callback(null, query, cypher_params);
303 | };
304 |
305 | // friend random user
306 | var _friendRandom = function (params, callback) {
307 | var cypher_params = {
308 | id: params.id,
309 | n: params.n
310 | };
311 |
312 | var query = [
313 | 'MATCH (user:User {id:{id}}), (friend:User)',
314 | 'WHERE NOT((user)-[:friend]-(friend)) AND NOT(user = friend)',
315 | 'WITH user, friend, rand() as rnd',
316 | 'ORDER BY rnd',
317 | 'LIMIT {n}',
318 | 'CREATE (user)-[:friend {created: timestamp()}]->(friend)',
319 | 'RETURN user, COLLECT(friend) as friends'
320 | ].join('\n');
321 | callback(null, query, cypher_params);
322 | };
323 |
324 | // unfriend the user
325 | var _unfriend = function (params, callback) {
326 | var cypher_params = {
327 | id: params.id,
328 | friend_id: params.friend_id
329 | };
330 |
331 | var query = [
332 | 'MATCH (user:User {id:{id}})-[r:friend]-(friend:User {id:{friend_id}})',
333 | 'DELETE r',
334 | 'RETURN user, friend'
335 | ].join('\n');
336 | callback(null, query, cypher_params);
337 | };
338 |
339 | // match with friends
340 | var _matchWithFriends = function (params, callback) {
341 | var cypher_params = {
342 | id: params.id
343 | };
344 |
345 | var query = [
346 | 'MATCH (user:User {id:{id}})',
347 | 'OPTIONAL MATCH (user)-[r:friend]-(friend:User)',
348 | 'RETURN user, COLLECT(friend) as friends'
349 | ].join('\n');
350 | callback(null, query, cypher_params);
351 | };
352 |
353 | // match with friends and friends of friends (FOF)
354 | var _matchWithFriendsAndFOF = function (params, callback) {
355 | var cypher_params = {
356 | id: params.id
357 | };
358 |
359 | var query = [
360 | 'MATCH (user:User {id:{id}})',
361 | 'OPTIONAL MATCH (user)-[:friend]-(friend:User)',
362 | 'OPTIONAL MATCH (friend:User)-[:friend]-(fof:User)',
363 | 'WHERE NOT(user=fof)',
364 | 'RETURN user, friend, COLLECT(fof) as fofs'
365 | ].join('\n');
366 | callback(null, query, cypher_params);
367 | };
368 |
369 | // match with friends of friends (FOF)
370 | var _matchWithFOF = function (params, callback) {
371 | var cypher_params = {
372 | id: params.id
373 | };
374 |
375 | var query = [
376 | 'MATCH (user:User {id:{id}})',
377 | 'OPTIONAL MATCH (user)-[:friend]-(friend:User)',
378 | 'OPTIONAL MATCH (friend:User)-[:friend]-(fof:User)',
379 | 'WHERE NOT(user=fof)',
380 | 'RETURN user, COLLECT(DISTINCT fof) as fofs'
381 | ].join('\n');
382 | callback(null, query, cypher_params);
383 | };
384 |
385 | // match all with friends
386 | var _matchAllWithFriends = function (params, callback) {
387 | var cypher_params = {};
388 |
389 | var query = [
390 | 'MATCH (user:User)',
391 | 'OPTIONAL MATCH (user)-[r:friend]-(friend:User)',
392 | 'RETURN user, COLLECT(friend) as friends'
393 | ].join('\n');
394 | callback(null, query, cypher_params);
395 | };
396 |
397 |
398 |
399 | // exposed functions
400 |
401 |
402 | // get a single user by id
403 | var getById = new Construct(_matchByUUID).query().then(_singleUser);
404 |
405 | // get a single user by name
406 | var getByName = new Construct(_matchByName).query().then(_singleUser);
407 |
408 | // get n random users
409 | var getRandom = new Construct(_getRandom).query().then(_manyUsers);
410 |
411 | // get n random users
412 | var getRandomWithFriends = new Construct(_getRandomWithFriends).query().then(_manyUsersWithFriends);
413 |
414 | // get a user by id and update their name
415 | var updateName = new Construct(_updateName, _singleUser);
416 |
417 | // create a new user
418 | var create = new Construct(_create, _singleUser);
419 |
420 | var _createManySetup = function (params, callback) {
421 | if (params.names && _.isArray(params.names)) {
422 | callback(null, _.map(params.names, function (name) {
423 | return {name: name};
424 | }));
425 | } else if (params.users && _.isArray(params.users)) {
426 | callback(null, _.map(params.users, function (user) {
427 | return _.pick(user, 'name', 'id');
428 | }));
429 | } else {
430 | callback(null, []);
431 | }
432 | };
433 |
434 | // create many new users
435 | var createMany = new Construct(_createManySetup).map(create);
436 |
437 | var _createRandomSetup = function (params, callback) {
438 | var names = _randomNames(params.n || 1);
439 | callback(null, {names: names});
440 | };
441 |
442 | var createRandom = new Construct(_createRandomSetup).then(createMany);
443 |
444 | // login a user
445 | var login = create;
446 |
447 | // get all users
448 | var getAll = new Construct(_matchAll, _manyUsers);
449 |
450 | // get all users count
451 | // var getAllCount = new Construct().query(_getAllCount).then(_singleCount);
452 | var getAllCount = new Construct(_getAllCount).query().then(_singleCount);
453 |
454 | // friend a user by id
455 | var friendUser = new Construct(_friend).query().then(_singleUserWithFriend);
456 |
457 | // friend random users
458 | var friendRandomUser = new Construct(_friendRandom, _singleUserWithFriends);
459 |
460 | // creates n new friendships between users
461 | var _assignManyFriendships = function (params, callback) {
462 | // number of friendships to create
463 | var friendships = parseInt(params.friendships || params.n || 1, 10);
464 |
465 | // user params
466 | var users = _.map(params.users, function (user) {
467 | return {
468 | id: user.id || user,
469 | n: 0
470 | };
471 | });
472 | var length = users.length;
473 |
474 | // randomly distribute friendships between users
475 | while (length && friendships>0) {
476 | friendships--;
477 | _.sample(users).n++;
478 | }
479 |
480 | users = _.filter(users, function (user) {
481 | return user.n > 0;
482 | });
483 | callback(null, users);
484 | };
485 |
486 | var manyFriendships = new Construct(_assignManyFriendships).mapSeries(friendRandomUser);
487 |
488 | // merge initParams and params
489 | var _manyFriendshipsSetup = function (params, callback) {
490 | callback(null, {
491 | users: params,
492 | friendships: this.params.friendships || this.params.n
493 | });
494 | };
495 |
496 | // creates many friendships between random users
497 | var manyRandomFriendships = new Construct(getRandom).then(_manyFriendshipsSetup).then(manyFriendships);
498 |
499 | // unfriend a user by id
500 | var unfriendUser = new Construct(_unfriend, _singleUserWithFriend);
501 |
502 | // delete a user by id
503 | var deleteUser = new Construct(_delete);
504 |
505 | // delete a user by id
506 | var deleteAllUsers = new Construct(_deleteAll);
507 |
508 | // reset all users
509 | var resetUsers = new Construct(deleteAllUsers)
510 | .params()
511 | .then(createRandom)
512 | .then(_manyFriendshipsSetup)
513 | .then(manyFriendships);
514 |
515 | // get a single user by id and all friends
516 | var getWithFriends = new Construct(_matchWithFriends, _singleUserWithFriends);
517 |
518 | // get a single user by id and all friends and friends of friends
519 | var getWithFriendsAndFOF = new Construct(_matchWithFriendsAndFOF, _singleUserWithFriendsAndFOF);
520 |
521 | // get a single user by id and all friends of friends
522 | var getWithFOF = new Construct(_matchWithFOF, _singleUserWithFOF);
523 |
524 | // get all users and all friends
525 | var getAllWithFriends = new Construct(_matchAllWithFriends, _manyUsersWithFriends);
526 |
527 |
528 |
529 | // export exposed functions
530 |
531 | module.exports = {
532 | getById: getById.done(),
533 | getByName: getByName.done(),
534 | getRandom: getRandom.done(),
535 | getRandomWithFriends: getRandomWithFriends.done(),
536 | updateName: updateName.done(),
537 | create: create.done(),
538 | createMany: createMany.done(),
539 | createRandom: createRandom.done(),
540 | login: login.done(),
541 | getAll: getAll.done(),
542 | getAllCount: getAllCount.done(),
543 | friendUser: friendUser.done(),
544 | friendRandomUser: friendRandomUser.done(),
545 | manyFriendships: manyFriendships.done(),
546 | manyRandomFriendships: manyRandomFriendships.done(),
547 | unfriendUser: unfriendUser.done(),
548 | deleteUser: deleteUser.done(),
549 | deleteAllUsers: deleteAllUsers.done(),
550 | resetUsers: resetUsers.done(),
551 | getWithFriends: getWithFriends.done(),
552 | getWithFriendsAndFOF: getWithFriendsAndFOF.done(),
553 | getWithFOF: getWithFOF.done(),
554 | getAllWithFriends: getAllWithFriends.done()
555 | };
--------------------------------------------------------------------------------
/routes/users.js:
--------------------------------------------------------------------------------
1 | // users.js
2 | // Routes to CRUD users.
3 |
4 | var Users = require('../models/users');
5 |
6 | var sw = require("swagger-node-express");
7 | var param = sw.params;
8 | var url = require("url");
9 | var swe = sw.errors;
10 | var _ = require('underscore');
11 |
12 |
13 | var swm = {
14 | deleted: function (name, res) {
15 | var msg = {
16 | code: 200,
17 | message: "deleted "+name
18 | };
19 | if (!res) {
20 | return msg;
21 | } else {
22 | res.send(msg.code, msg);
23 | }
24 | }
25 | };
26 |
27 | /*
28 | * Util Functions
29 | */
30 |
31 | function setHeaders (res, queries, start) {
32 | // sw.setHeaders(res);
33 | res.header('Duration-ms', new Date() - start);
34 | if (queries) {
35 | res.header('Neo4j', JSON.stringify(queries));
36 | }
37 | }
38 |
39 | function writeResponse (res, results, queries, start) {
40 | setHeaders(res, queries, start);
41 | res.send(results);
42 | }
43 |
44 | function parseUrl(req, key) {
45 | return url.parse(req.url,true).query[key];
46 | }
47 |
48 | function parseBool (req, key) {
49 | return 'true' == url.parse(req.url,true).query[key];
50 | }
51 |
52 |
53 | /*
54 | * API Specs and Functions
55 | */
56 |
57 | exports.list = {
58 | spec: {
59 | description : "List all users",
60 | path : "/users",
61 | method: "GET",
62 | summary : "Find all users",
63 | notes : "Returns all users",
64 | type: "array",
65 | items: {
66 | $ref: "User"
67 | },
68 | produces: ["application/json"],
69 | parameters : [
70 | param.query("friends", "Include friends", "boolean", false, ["true", "false"], "true")
71 | ],
72 | responseMessages: [swe.notFound('users')],
73 | nickname : "getUsers"
74 | },
75 | action: function (req, res) {
76 | var friends = parseBool(req, 'friends');
77 | var options = {
78 | neo4j: parseBool(req, 'neo4j')
79 | };
80 | var start = new Date();
81 |
82 | function callback (err, results, queries) {
83 | if (err || !results) throw swe.notFound('users');
84 | writeResponse(res, results, queries, start);
85 | }
86 |
87 | if (friends) {
88 | Users.getAllWithFriends(null, options, callback);
89 | } else {
90 | Users.getAll(null, options, callback);
91 | }
92 | }
93 | };
94 |
95 | exports.userCount = {
96 | spec: {
97 | description : "User count",
98 | path : "/users/count",
99 | notes : "User count",
100 | summary : "User count",
101 | method: "GET",
102 | parameters : [],
103 | type : "Count",
104 | responseMessages : [swe.notFound('users')],
105 | nickname : "userCount"
106 | },
107 | action: function (req, res) {
108 | var options = {
109 | neo4j: parseBool(req, 'neo4j')
110 | };
111 | var start = new Date();
112 | Users.getAllCount(null, options, function (err, results, queries) {
113 | // if (err || !results) throw swe.notFound('users');
114 | writeResponse(res, results, queries, start);
115 | });
116 | }
117 | };
118 |
119 | exports.addUser = {
120 | spec: {
121 | path : "/users",
122 | notes : "adds a user to the graph",
123 | summary : "Add a new user to the graph",
124 | method: "POST",
125 | type : "array",
126 | items : {
127 | $ref: "User"
128 | },
129 | parameters : [
130 | param.query("name", "User name, seperate multiple names by commas", "string", true)
131 | ],
132 | responseMessages : [swe.invalid('input')],
133 | nickname : "addUser"
134 | },
135 | action: function(req, res) {
136 | var options = {
137 | neo4j: parseBool(req, 'neo4j')
138 | };
139 | var start = new Date();
140 | var names = _.invoke(parseUrl(req, 'name').split(','), 'trim');
141 | if (!names.length){
142 | throw swe.invalid('name');
143 | } else {
144 | Users.createMany({
145 | names: names
146 | }, options, function (err, results, queries) {
147 | if (err || !results) throw swe.invalid('input');
148 | writeResponse(res, results, queries, start);
149 | });
150 | }
151 | }
152 | };
153 |
154 |
155 | exports.addRandomUsers = {
156 | spec: {
157 | path : "/users/random/{n}",
158 | notes : "adds many random users to the graph",
159 | summary : "Add many random new users to the graph",
160 | method: "POST",
161 | type : "array",
162 | items : {
163 | $ref: "User"
164 | },
165 | parameters : [
166 | param.path("n", "Number of random users to be created", "integer", null, 1)
167 | ],
168 | responseMessages : [swe.invalid('input')],
169 | nickname : "addRandomUsers"
170 | },
171 | action: function(req, res) {
172 | var options = {
173 | neo4j: parseBool(req, 'neo4j')
174 | };
175 | var start = new Date();
176 | var n = parseInt(req.params.n, 10);
177 | if (!n){
178 | throw swe.invalid('input');
179 | } else {
180 | Users.createRandom({n:n}, options, function (err, results, queries) {
181 | if (err || !results) throw swe.invalid('input');
182 | writeResponse(res, results, queries, start);
183 | });
184 | }
185 | }
186 | };
187 |
188 |
189 | exports.findById = {
190 | spec: {
191 | description : "find a user",
192 | path : "/users/{id}",
193 | notes : "Returns a user based on ID",
194 | summary : "Find user by ID",
195 | method: "GET",
196 | parameters : [
197 | param.path("id", "ID of user that needs to be fetched", "string"),
198 | param.query("friends", "Include friends", "boolean", false, ["true", "false"], "true"),
199 | param.query("fof", "Include friends of friends", "boolean", false, ["true", "false"])
200 | ],
201 | type : "User",
202 | responseMessages : [swe.invalid('id'), swe.notFound('user')],
203 | nickname : "getUserById"
204 | },
205 | action: function (req,res) {
206 | var id = req.params.id;
207 | var options = {
208 | neo4j: parseBool(req, 'neo4j')
209 | };
210 | var start = new Date();
211 | var friends = parseBool(req, 'friends');
212 | var fof = parseBool(req, 'fof');
213 |
214 | if (!id) throw swe.invalid('id');
215 |
216 | var params = {
217 | id: id
218 | };
219 |
220 | var callback = function (err, results, queries) {
221 | if (err) throw swe.notFound('user');
222 | writeResponse(res, results, queries, start);
223 | };
224 |
225 | if (friends) {
226 | if (fof) {
227 | Users.getWithFriendsAndFOF(params, options, callback);
228 | } else {
229 | Users.getWithFriends(params, options, callback);
230 | }
231 | } else if (fof) {
232 | Users.getWithFOF(params, options, callback);
233 | } else {
234 | Users.getById(params, options, callback);
235 | }
236 | }
237 | };
238 |
239 | exports.getRandom = {
240 | spec: {
241 | description : "get random users",
242 | path : "/users/random/{n}",
243 | notes : "Returns n random users",
244 | summary : "Get random users",
245 | method: "GET",
246 | parameters : [
247 | param.path("n", "Number of random users get", "integer", null, 1),
248 | param.query("friends", "Include friends", "boolean", false, ["true", "false"], "true")
249 | ],
250 | type : "User",
251 | responseMessages : [swe.invalid('id'), swe.notFound('user')],
252 | nickname : "getRandomUsers"
253 | },
254 | action: function (req,res) {
255 | var n = parseInt(req.params.n, 10);
256 | var options = {
257 | neo4j: parseBool(req, 'neo4j')
258 | };
259 | var start = new Date();
260 | var friends = parseBool(req, 'friends');
261 |
262 | if (friends) {
263 | Users.getRandomWithFriends({n: n}, options, function (err, results, queries) {
264 | if (err) throw swe.notFound('users');
265 | writeResponse(res, results, queries, start);
266 | });
267 | } else {
268 | Users.getRandom({n: n}, options, function (err, results, queries) {
269 | if (err) throw swe.notFound('users');
270 | writeResponse(res, results, queries, start);
271 | });
272 | }
273 | }
274 | };
275 |
276 |
277 | exports.updateUser = {
278 | spec: {
279 | path : "/users/{id}",
280 | notes : "updates a user name",
281 | method: "PUT",
282 | summary : "Update an existing user",
283 | parameters : [
284 | param.path("id", "ID of user that needs to be fetched", "string"),
285 | param.query("name", "New user name", "string", true)
286 | ],
287 | responseMessages : [swe.invalid('id'), swe.notFound('user'), swe.invalid('input')],
288 | nickname : "updateUser"
289 | },
290 | action: function(req, res) {
291 | var options = {
292 | neo4j: parseBool(req, 'neo4j')
293 | };
294 | var start = new Date();
295 | var name = parseUrl(req, 'name').trim();
296 | var id = req.params.id;
297 | if (!id || !name.length){
298 | throw swe.invalid('user');
299 | }
300 | var params = {
301 | id: id,
302 | name: name
303 | };
304 | Users.updateName(params, options, function (err, results, queries) {
305 | if (err) throw swe.invalid('id');
306 | if (!results) throw swe.invalid('user');
307 | writeResponse(res, results, queries, start);
308 | });
309 | }
310 | };
311 |
312 |
313 | exports.deleteUser = {
314 | spec: {
315 | path : "/users/{id}",
316 | notes : "removes a user from the db",
317 | method: "DELETE",
318 | summary : "Remove an existing user",
319 | parameters : [
320 | param.path("id", "ID of user that needs to be removed", "string")
321 | ],
322 | responseMessages : [
323 | swm.deleted('user'),
324 | swe.invalid('id'),
325 | swe.notFound('user')
326 | ],
327 | nickname : "deleteUser"
328 | },
329 | action: function(req, res) {
330 | var id = req.params.id;
331 | if (!id) throw swe.invalid('id');
332 | var options = {
333 | neo4j: parseBool(req, 'neo4j')
334 | };
335 | var start = new Date();
336 |
337 | Users.deleteUser({id: id}, options, function (err, results, queries) {
338 | setHeaders(res, queries, start);
339 | if (err) throw swe.invalid('user');
340 | console.log(swm.deleted('user'));
341 | // throw swm.deleted('user');
342 | swm.deleted('user', res);
343 | // res.send(200,"Deleted");
344 | });
345 | }
346 | };
347 |
348 |
349 | exports.deleteAllUsers = {
350 | spec: {
351 | path : "/users",
352 | notes : "removes all users from the db",
353 | method: "DELETE",
354 | summary : "Removes all users",
355 | responseMessages:[
356 | {
357 | code: 200,
358 | message: "Deleted"
359 | },
360 | swe.invalid('user')
361 | ],
362 | parameters : [],
363 | // type: 'code', // does this work?
364 | nickname : "deleteAllUsers"
365 | },
366 | action: function(req, res) {
367 | var options = {
368 | neo4j: parseBool(req, 'neo4j')
369 | };
370 | var start = new Date();
371 | Users.deleteAllUsers(null, options, function (err, results, queries) {
372 | setHeaders(res, queries, start);
373 | if (err) throw swe.invalid('user');
374 | // res.send(200);
375 | swm.deleted('users', res);
376 | });
377 | }
378 | };
379 |
380 | exports.resetUsers = {
381 | spec: {
382 | path : "/users",
383 | notes : "Resets the graph with new users and friendships",
384 | method: "PUT",
385 | summary : "Removes all users and then adds n random users",
386 | responseMessages : [swe.invalid('user'), swe.invalid('input')],
387 | type : "array",
388 | items : {
389 | $ref: "User"
390 | },
391 | parameters : [
392 | param.query("n", "Number of random users to be created", "integer", null, null, 10),
393 | param.query("f", "Average number of friendships per user", "integer", false, ["0","1","2","3"], "2")
394 | ],
395 | nickname : "resetUsers"
396 | },
397 | action: function(req, res) {
398 | var options = {
399 | neo4j: parseBool(req, 'neo4j')
400 | };
401 | var start = new Date();
402 | var n = parseInt(parseUrl(req, 'n'), 10) || 10;
403 | var f = parseInt(parseUrl(req, 'f'), 10) || 2;
404 | var friendships = Math.round(f*n/2);
405 | Users.resetUsers({n: n, friendships: friendships}, options, function (err, results, queries) {
406 | if (err || !results) throw swe.invalid('input');
407 | writeResponse(res, results, queries, start);
408 | });
409 | }
410 | };
411 |
412 |
413 | exports.friendRandomUser = {
414 | spec: {
415 | path : "/users/{id}/friend/random/{n}",
416 | notes : "friends a random user",
417 | method: "POST",
418 | summary : "Randomly friend an existing user",
419 | parameters : [
420 | param.path("id", "ID of the user", "string"),
421 | param.path("n", "Number of new friends", "integer", ["1","2","3","4","5"], "1")
422 | ],
423 | responseMessages : [swe.invalid('id'), swe.notFound('user'), swe.invalid('input')],
424 | nickname : "friendRandomUser"
425 | },
426 | action: function(req, res) {
427 | var options = {
428 | neo4j: parseBool(req, 'neo4j')
429 | };
430 | var start = new Date();
431 | var id = req.params.id;
432 | var n = parseInt(req.params.n) || 1;
433 | if (!id) {
434 | throw swe.invalid('user');
435 | }
436 | var params = {
437 | id: id,
438 | n: n
439 | };
440 | Users.friendRandomUser(params, options, function (err, results, queries) {
441 | if (err) throw swe.invalid('id');
442 | if (!results) throw swe.invalid('user');
443 | writeResponse(res, results, queries, start);
444 | });
445 | }
446 | };
447 |
448 | exports.friendUser = {
449 | spec: {
450 | path : "/users/{id}/friend/{friend_id}",
451 | notes : "friends a user by ID",
452 | method: "POST",
453 | summary : "Friend an existing user",
454 | parameters : [
455 | param.path("id", "ID of the user", "string"),
456 | param.path("friend_id", "ID of the user to be friended", "string")
457 | ],
458 | responseMessages : [swe.invalid('id'), swe.invalid('friend_id'), swe.notFound('user'), swe.invalid('input')],
459 | nickname : "friendUser"
460 | },
461 | action: function(req, res) {
462 | var options = {
463 | neo4j: parseBool(req, 'neo4j')
464 | };
465 | var start = new Date();
466 | var id = req.params.id;
467 | var friend_id = req.params.friend_id;
468 | if (!id) {
469 | throw swe.invalid('user');
470 | }
471 | if (!friend_id || friend_id == id) {
472 | throw swe.invalid('friend_id');
473 | }
474 | var params = {
475 | id: id,
476 | friend_id: friend_id
477 | };
478 | Users.friendUser(params, options, function (err, results, queries) {
479 | if (err) throw swe.invalid('id');
480 | if (!results) throw swe.invalid('user');
481 | writeResponse(res, results, queries, start);
482 | });
483 | }
484 | };
485 |
486 |
487 | exports.manyRandomFriendships = {
488 | spec: {
489 | path : "/users/random/friend/{n}",
490 | notes : "creates n random friendships",
491 | method: "POST",
492 | summary : "Create many random friendships",
493 | parameters : [
494 | param.path("n", "Number of random users", "integer", null, "1")
495 | ],
496 | responseMessages : [swe.notFound('users')],
497 | nickname : "manyRandomFriendships"
498 | },
499 | action: function(req, res) {
500 | var options = {
501 | neo4j: parseBool(req, 'neo4j')
502 | };
503 | var start = new Date();
504 | var n = parseInt(req.params.n, 10) || 1;
505 | var params = {
506 | n: n
507 | };
508 | Users.manyRandomFriendships(params, options, function (err, results, queries) {
509 | if (err) throw swe.notFound('users');
510 | writeResponse(res, results, queries, start);
511 | });
512 | }
513 | };
514 |
515 |
516 | exports.unfriendUser = {
517 | spec: {
518 | path : "/users/{id}/unfriend/{friend_id}",
519 | notes : "unfriend a user by ID",
520 | method: "POST",
521 | summary : "Unfriend an existing user",
522 | parameters : [
523 | param.path("id", "ID of the user", "string"),
524 | param.path("friend_id", "ID of the user to be unfriended", "string")
525 | ],
526 | responseMessages : [swe.invalid('id'), swe.invalid('friend_id'), swe.notFound('user'), swe.invalid('input')],
527 | nickname : "unfriendUser"
528 | },
529 | action: function(req, res) {
530 | var options = {
531 | neo4j: parseBool(req, 'neo4j')
532 | };
533 | var start = new Date();
534 | var id = req.params.id;
535 | var friend_id = req.params.friend_id;
536 | if (!id) {
537 | throw swe.invalid('user');
538 | }
539 | if (!friend_id || friend_id == id) {
540 | throw swe.invalid('friend_id');
541 | }
542 | var params = {
543 | id: id,
544 | friend_id: friend_id
545 | };
546 | Users.unfriendUser(params, options, function (err, results, queries) {
547 | if (err) throw swe.invalid('id');
548 | if (!results) throw swe.invalid('user');
549 | writeResponse(res, results, queries, start);
550 | });
551 | }
552 | };
--------------------------------------------------------------------------------