├── 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 | }; --------------------------------------------------------------------------------