Commit 07ee148d authored by Patiphan Marak's avatar Patiphan Marak

ขอ 2 คะแนน

parents
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: 'สวัสดีคุณ'
});
rl.prompt();
rl.on('line', (line) => {
switch (line.trim()) {
case 'hello':
console.log('world!');
break;
default:
console.log(`สวัสดีคุณ '${line.trim()}'`);
break;
}
rl.prompt();
})
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
node_modules
npm-debug.log
sudo: false
language: node_js
node_js:
- "5"
- "4"
- "0.12"
- "0.10"
script: "npm test"
services:
- mongodb
version 0.4.0 - 2015-01-14
* Warning: probably incompatible with 0.3.x because of updated dependencies
* Complete move to express 4.x API
* Move to mongodb 2.x
* Move to mongoose 4.x
* Add 'unauthorized' http status, as mentioned in docs
* Add 'status()' helper to hooks 'next' callback, as mentioned in docs
version 0.3.7 - 2014-05-05
* Enable overriding mongoose collection paths (fixes #1)
version 0.3.6 - 2014-04-23
* Enable creating separate yarm instances
version 0.3.5 - 2014-03-06
* Fixed not being able to POST to mongoose scalar array fields
version 0.3.4 - 2014-03-01
* Fixed a wrong implementation of mongoose and native hooks that prevented
overriding subresources
* Fixed error when requesting aggregate collections with a limit of 0
version 0.3.3 - 2014-02-14
* Add 'postResponse' option to mongoose and native resources
* Allow using a custom request handler with cb.custom(function(req, res) { ... });
* Add readonly helper to all resources
version 0.3.2 - 2014-01-30
* Fixed mongoose helper directly passing yarm callback to mongoose
* URL-decode wildcard-matched parameters (except for "*")
version 0.3.0 - 2014-01-24
* Full rewrite and API change
version 0.2.0 - 2013-08-11
* Added 'key' option to mongoose resources
* Added mongoose aggregate support
* Added custom queries to DocumentArrays
version 0.1.1 - 2013-08-03
* Improved mongoose resource handling
* Fixed bugs with .get/.list precedence
* Added custom queries to mongoose collections
* Rewrote native resources implementation
* Added new tests
version 0.0.6 - 2013-08-02
* Fixed a bug with readable streams
* Added tests
version 0.0.5 - 2013-08-01
* Initial release
The MIT License (MIT)
Copyright (c) 2013-2016 Nicolas Joyard
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
yarm
====
*Yet Another REST Middleware for node.js, Express and mongoose.*
Master branch: [![Build Status](https://travis-ci.org/njoyard/yarm.png?branch=master)](https://travis-ci.org/njoyard/yarm)
Development branch: [![Build Status](https://travis-ci.org/njoyard/yarm.png?branch=devel)](https://travis-ci.org/njoyard/yarm)
## Installation
Use npm to install yarm, or add yarm to your package.json dependencies.
```
$ npm install yarm
```
yarm has no dependencies, however it is intended to be used with Express and will have additional features if mongoose is present.
## Usage
Below is a short introduction to yarm usage, see the [complete documentation][doc] for more information.
### Basics
Use yarm as any other Express middleware.
```javascript
var app = require("express")();
var yarm = require("yarm");
app.use("/rest", yarm());
app.listen(80);
```
### Serving native javascript resources
Use `yarm.native()` to serve native Javascript objects or arrays.
```javascript
var app = require("express")();
var yarm = require("yarm");
app.use("/rest", yarm());
yarm.native("me", {
name: "Alice",
age: 30
});
yarm.native("friends", [
"Bob",
"Charlie"
]);
app.listen(80);
```
```
$ curl http://localhost/rest/me
{
"name": "Alice",
"age": 30
}
$ curl http://localhost/rest/me/name
Alice
$ curl http://localhost/rest/friends
{
"_count": 2,
"_items": [ "Bob", "Charlie" ]
}
$ curl http://localhost/rest/friends/1
Charlie
```
Head on to the documentation on [Native resources][doc-native] for more details.
### Serving mongoose models and aggregates
When mongoose is available, you can use `yarm.mongoose()` to serve models.
```javascript
var app = require("express")();
var yarm = require("yarm");
var mongoose = require("mongoose");
app.use("/rest", yarm());
var postSchema = new mongoose.Schema({
title: String,
text: String,
comments: [{
author: String,
text: String
}]
});
var Post = mongoose.model("post", postSchema);
yarm.mongoose("posts", Post);
app.listen(80);
```
```
$ curl http://localhost/rest/posts?skip=10&limit=1
{
"_count": 42,
"_items": [
{
"_id": "507f191e810c19729de860ea",
"title": "My 11th post",
"text": "Hello, World",
"comments": [
{
"author": "Bob",
"text": "First !"
}
]
}
]
}
$ curl http://localhost/rest/posts/507f191e810c19729de860ea
{
"_id": "507f191e810c19729de860ea",
"title": "My 11th post",
"text": "Hello, World",
"comments": [
{
"author": "Bob",
"text": "First !"
}
]
}
$ curl http://localhost/rest/posts/507f191e810c19729de860ea/comments/0/text
First !
```
Head on to the documentation on [Mongoose resources][doc-mongoose] for more details.
### Serving custom resources
Use `yarm.resource` to define resources with custom handlers.
```javascript
var app = require("express")(),
var yarm = require("yarm");
yarm.resource("greeting")
.get(function(req, cb) {
cb(null, { hello: "world" });
})
.sub("french")
.get(function(req, cb) {
cb(null, { bonjour: "tout le monde" });
});
yarm.resource("greeting/pirate")
.get(function(req, cb) {
cb(null, { arrrrr: "arrrrrr" });
});
app.use("/rest", yarm());
app.listen(80);
```
```
$ curl http://localhost/rest/greeting
{
"hello": "world"
}
$ curl http://localhost/rest/greeting/french
{
"bonjour": "tout le monde"
}
$ curl http://localhost/rest/greeting/pirate
{
"arrrrr": "arrrrrr"
}
```
Head on to the documentation on [Custom resources][doc-custom] for more details.
### Extending served resources
yarm allows adding and replacing handlers for any resource or sub-resource. This enables restricting or extending the behaviour of the default native and mongoose resource handlers, as well as defining very complex custom resource hierarchies.
```javascript
yarm.<whatever>()
.get(function(req, cb) {
// Override GET handler here
});
function notAllowed(req, cb) {
cb(null, "Nope, sorry :(");
}
yarm.native("readonly", myObject)
.put(notAllowed)
.post(notAllowed)
.del(notAllowed)
.sub("*")
.put(notAllowed)
.post(notAllowed)
.del(notAllowed);
yarm.resource("already/defined/path")
.get(function(req, cb) {
// Will not alter 'already' nor 'already/defined' handlers,
// nor those for 'already/defined/other' if they are defined
});
```
Head on to the documentation on [Extending resources][doc-extend] for more details.
## Contributing
yarm is published under the terms of the MIT license. Feel free to report bugs or send pull requests.
[doc]: http://yarm.njoyard.fr
[doc-native]: http://yarm.njoyard.fr/doc-native-resources.html
[doc-mongoose]: http://yarm.njoyard.fr/doc-mongoose-resources.html
[doc-custom]: http://yarm.njoyard.fr/doc-custom-resources.html
[doc-extend]: http://yarm.njoyard.fr/doc-extending-resources.html
This diff is collapsed.
/*jshint node:true */
module.exports = require("./lib");
\ No newline at end of file
/*jshint node:true */
"use strict";
var data = {
created: { code: 201, body: "Created" },
noContent: { code: 204, body: "" },
badRequest: { code: 400, body: "Bad request" },
unauthorized: {code: 401, body: "Unauthorized"},
notFound: { code: 404, body: "Not found" },
methodNotAllowed: { code: 405, body: "Method not allowed" },
notImplemented: { code: 501, body: "Not implemented"}
};
function HTTPStatus(code, message) {
var err = new Error(message);
err.code = code;
return err;
}
HTTPStatus.names = Object.keys(data);
HTTPStatus.names.forEach(function(key) {
HTTPStatus[key] = HTTPStatus.bind(null, data[key].code, data[key].body);
});
module.exports = HTTPStatus;
\ No newline at end of file
/*jshint node:true */
"use strict";
var yarm = require("./yarm");
var hasMongoose = false;
try {
require("mongoose");
hasMongoose = true;
} catch(e) {}
function instanciate() {
var instance = yarm();
/* Add native extension */
instance.extend("native", require("./native"));
/* Add mongoose extensions if mongoose is present */
if (hasMongoose) {
instance.extend("mongoose", require("./mongoose/model"));
instance.extend("aggregate", require("./mongoose/aggregate"));
}
return instance;
}
module.exports = instanciate();
module.exports.newInstance = instanciate;
/*jshint node:true*/
"use strict";
var queryHelpers = require("./query"),
mongodb = require("mongodb"),
ObjectId = mongodb.ObjectID;
/*!
* Misc helpers
*/
/* Create an aggregate pipeline from the base pipeline, the request query if
any, and contextual additions */
function createPipeline(req, pipeline, additions) {
pipeline = pipeline.slice(0);
if (req && req.query["query"]) {
pipeline.push({ $match: queryHelpers.create(req.query["query"]) });
}
for (var i = 0, len = additions.length; i < len; i++) {
pipeline.push(additions[i]);
}
return pipeline;
}
/*!
* Aggregate collection helpers
*/
function aggregateCollCount(req, cb) {
var model = req.mongoose.model;
var pipeline = req.mongoose.pipeline;
var args = createPipeline(req, pipeline, [
{ $group: { _id: 0, count: { $sum: 1 } } }
]);
args.push(function(err, result) {
if (err) {
cb(err);
} else {
cb(null, result.length ? result[0].count : 0);
}
});
model.aggregate.apply(model, args);
}
function aggregateCollList(req, offset, limit, cb) {
var model = req.mongoose.model;
var pipeline = req.mongoose.pipeline;
var additions = [{ $skip: offset }];
if (limit > 0) {
additions.push({ $limit: limit });
}
var args = createPipeline(req, pipeline, additions);
args.push(function(err, items) {
if (err) {
cb(err);
} else {
cb(null, items);
}
});
model.aggregate.apply(model, args);
}
/*!
* Aggregated document helpers
*/
function aggregateDocHook(req, next) {
var oid, match;
var id = req.params.projectedId;
var pipeline = req.mongoose.pipeline;
var model = req.mongoose.model;
try {
oid = new ObjectId(id);
match = { $or: [{ "_id": id }, { "_id": oid }] };
} catch(e) {
// Invalid ObjectID
match = { "_id": id };
}
var args = createPipeline(null, pipeline, [
{ $match: match },
{ $limit: 1 }
]);
args.push(function(err, result) {
if (err) {
next(err);
} else if (result.length) {
req.mongoose.item = result[0];
next();
} else {
next();
}
});
model.aggregate.apply(model, args);
}
function aggregateDocGet(req, cb) {
if (!("item" in req.mongoose)) {
return cb.notFound();
}
cb(null, req.mongoose.item);
}
/*!
* Aggregate resource definition helper
*/
function aggregateResource(name, Model, pipeline) {
var collResource = this.sub(name)
.hook(function aggregateHook(req, next) {
req.mongoose = { model: Model, pipeline: pipeline };
next();
})
.count(aggregateCollCount)
.list(aggregateCollList);
collResource.sub(":projectedId")
.hook(aggregateDocHook)
.get(aggregateDocGet);
return collResource;
}
module.exports = aggregateResource;
/*jshint node:true*/
"use strict";
var queryHelpers = require("./query"),
mongoose = require("mongoose"),
CastError = mongoose.SchemaType.CastError;
/*!
* Misc helpers
*/
function getObject(req, item) {
if (typeof item.toObject === "function") {
item._request = req;
return item.toObject(req.options.toObject);
} else {
return item;
}
}
/*!
* Document resource handlers
*/
function mongooseCollCount(req, cb) {
var query = req.options.query();
if (req.query["query"]) {
// Cache query operator
if (!req._queryOperator) {
req._queryOperator = queryHelpers.create(req.query["query"]);
}
query = query.find(req._queryOperator);
}
query.count(function(err, count) { cb(err, count); });
}
function mongooseCollList(req, offset, limit, cb) {
var options = req.options;
var query = options.query();
if (req.query["query"]) {
// Cache query operator
if (!req._queryOperator) {
req._queryOperator = queryHelpers.create(req.query["query"]);
}
query = query.find(req._queryOperator);
}
query = query.skip(offset).limit(limit);
if (req.query["sort"]) {
query = query.sort(req.query["sort"]);
} else if (options.sort) {
query = query.sort(options.sort);
}
return query.exec(function(err, items) {
if (err) {
cb(err);
} else {
cb(null, items.map(function(item) {
var obj = getObject(req, item);
return obj;
}));
}
});
}
function mongooseCollPost(req, cb) {
var model = req.mongoose.model;
model.create(req.body, function(err, doc) {
if (err) {
cb(err);
} else {
if (req.options.postResponse) {
cb(null, getObject(req, doc));
} else {
cb.created();
}
}
});
}
/*!
* Document resource handlers
*/
function mongooseDocHook(req, next) {
var options = req.options;
var crit = {};
crit[options.key] = req.params.id;
req.mongoose.path += "/" + req.params.id;
options.query().find(crit).findOne(function(err, item) {
if (err instanceof CastError) {
// id is not valid, just continue without saving item
return next();
}
if (err) {
return next(err);
}
req.mongoose.doc = item;
next();
});
}
function mongooseDocGet(req, cb) {
if (req.mongoose.doc) {
cb(null, getObject(req, req.mongoose.doc));
} else {
cb.notFound();
}
}
function mongooseDocPut(req, isPatch, cb) {
var doc = req.mongoose.doc;
if (!doc) {
return cb.notFound();
}
doc.set(req.body);
doc.save(function(err) {
cb(err);
});
}
function mongooseDocDel(req, cb) {
if (!req.mongoose.doc) {
return cb.notFound();
}
req.mongoose.doc.remove(function(err) {
cb(err);
});
}
/*!
* Document path resource handlers
*/
function mongoosePathHook(req, next) {
var doc = req.mongoose.doc;
var docpath = req.mongoose.path;
var subkeys = req.options.subkeys;
if (!doc) {
// We have no doc in the first place, don't try to find member
return next();
}
var path = req.params["*"];
var parts = path.split("/");
var fullpath = docpath;
var current = doc;
var parent = doc;
var link = {};
while(parts.length > 0) {
var part = parts.shift();
fullpath += "/" + part;
var decoded = decodeURIComponent(part);
if (current.isMongooseDocumentArray) {
parent = current;
var key = "_id";
if (subkeys) {
if (typeof subkeys === "string") {
key = subkeys;
} else {
Object.keys(subkeys).forEach(function(pattern) {
if (req.match(pattern, fullpath)) {
key = subkeys[pattern];
}
});
}
}
if (key !== "_id") {
current = current.filter(function(item) {
return item[key] === decoded;
})[0];
link = { id: current._id };
} else {
current = current.id(decoded);
link = { id: decoded };
}
} else {
if ("field" in link) {
link.field += "." + decoded;
} else {
parent = current;
link = { field: decoded };
}
current = parent.get(link.field);
}
if (!current) {
return next();
}
}
req.mongoose.parent = parent;
req.mongoose.item = current;
req.mongoose.link = link;
next();
}
function mongoosePathGet(req, cb) {
if (!("item" in req.mongoose)) {
return cb.notFound();
}
var item = req.mongoose.item;
if (item.isMongooseDocumentArray) {
cb.list(mongooseDocArrayCount, mongooseDocArrayList);
} else {
cb(null, getObject(req, item));
}
}
function mongoosePathPut(req, isPatch, cb) {
if (!("item" in req.mongoose)) {
return cb.notFound();
}
var parent = req.mongoose.parent;
var link = req.mongoose.link;
var doc = req.mongoose.doc;
var value = req.body;
if ("_value" in value) {
value = value._value;
}
if ("id" in link) {
parent.id(link.id).set(value);
} else if ("field" in link) {
parent.set(link.field, value);
} else {
return cb(new Error("Unknown link type"));
}
doc.save(function(err) {
cb(err);
});
}
function mongoosePathDel(req, cb) {
if (!("item" in req.mongoose)) {
return cb.notFound();
}
var parent = req.mongoose.parent;
var link = req.mongoose.link;
var doc = req.mongoose.doc;
if ("id" in link) {
parent.splice(parent.indexOf(parent.id(link.id)), 1);
} else if ("field" in link) {
parent.set(link.field, undefined);
} else {
return cb(new Error("Unknown link type"));
}
doc.save(function(err) {
cb(err);
});
}
function mongoosePathPost(req, cb) {
if (!("item" in req.mongoose)) {
return cb.notFound();
}
var item = req.mongoose.item;
if (item.isMongooseDocumentArray) {
mongooseDocArrayPost(req, cb);
} else if (Array.isArray(item)) {
if ("_value" in req.body) {
req.body = req.body._value;
}
mongooseDocArrayPost(req, cb);
} else {
return cb.methodNotAllowed();
}
}
/*!
* Mongoose DocumentArray helpers
*/
function queryDocArray(req) {
var docArray = req.mongoose.item;
if (req.query["query"]) {
// Cache query result
if (!req.mongoose._queryResult) {
req.mongoose._queryResult = docArray.filter(
queryHelpers.match.bind(
null,
queryHelpers.create(req.query["query"])
)
);
}
return req.mongoose._queryResult;
} else {
return docArray;
}
}
function mongooseDocArrayCount(req, cb) {
var len = queryDocArray(req).length;
cb(null, len);
}
function mongooseDocArrayList(req, offset, limit, cb) {
var items = queryDocArray(req);
if (limit > 0) {
items = items.slice(offset, offset + limit);
} else {
items = items.slice(offset);
}
cb(null, items);
}
function mongooseDocArrayPost(req, cb) {
var docArray = req.mongoose.item;
var doc = req.mongoose.doc;
var index = NaN;
if (req.query["index"]) {
index = Number(req.query["index"]);
}
if (isNaN(index)) {
index = docArray.length;
}
docArray.splice(Math.max(0, Math.min(docArray.length, index)), 0, req.body);
doc.save(function(err) {
if (err) {
cb(err);
} else {
if (req.options.postResponse) {
cb(null, getObject(req, docArray[index]));
} else {
cb.created();
}
}
});
}
/*!
* Mongoose resource definition helper
*/
function mongooseResource(name, Model) {
/*jshint validthis:true*/
var collResource = this.sub(name)
.hook(function modelHook(req, next) {
req.mongoose = { model: Model, path: name };
next();
})
.count(mongooseCollCount)
.list(mongooseCollList)
.post(mongooseCollPost)
.set("query", function mongooseDefaultQuery() { return Model.find(); })
.set("key", "_id");
var docResource = collResource.sub(":id")
.hook(mongooseDocHook)
.get(mongooseDocGet)
.put(mongooseDocPut)
.del(mongooseDocDel);
docResource.sub("*", mongoosePathHook)
.get(mongoosePathGet)
.put(mongoosePathPut)
.del(mongoosePathDel)
.post(mongoosePathPost);
return collResource;
}
module.exports = mongooseResource;
/*jshint node:true*/
"use strict";
/*!
* Search query helpers
*/
var queryRegex = /^\/(.*)\/([imx]*)$/;
/* Generate a mongoose query operator from a ?query= request parameter */
function createQueryOperator(query) {
var or = {
$or: query.split(" OR ").map(function(orOperand) {
var and = {
$and: orOperand.split(" AND ").map(function(andOperand) {
var colonIndex = andOperand.indexOf(":"),
bangIndex = andOperand.indexOf("!");
if (colonIndex === -1 && bangIndex === -1) {
// Invalid operator, skip
return {};
}
var match = andOperand.match(/^([^!:]+)([!:])(.*)$/);
var field = match[1];
var negate = match[2] === "!";
var value = match[3];
var operator = {};
var op, matches;
matches = value.match(queryRegex);
if (matches) {
op = new RegExp(matches[1], matches[2]);
operator[field] = negate ? { $not: op } : op;
} else {
if (negate) {
// Mongoose does not handle { $not: "value" }
operator[field] = { $nin: [value] };
} else {
operator[field] = value;
}
}
return operator;
}).filter(function(operator) {
return Object.keys(operator).length > 0;
})
};
return and.$and.length === 1 ? and.$and[0] : and;
})
};
return or.$or.length === 1 ? or.$or[0] : or;
}
/* Get property path value in a document or in a plain object */
function getPath(obj, path) {
if (typeof obj.get === "function") {
return obj.get(path);
}
var parts = path.split(".");
while (parts.length) {
if (!obj) {
return;
}
obj = obj[parts.shift()];
}
return obj;
}
/* Match a mongoose query criterion to a document */
function matchQueryCriterion(crit, doc) {
return Object.keys(crit).every(function(path) {
var value = getPath(doc, path) || "",
match = crit[path],
negate = false,
result;
if (typeof match === "string") {
result = value.toString() === match;
} else {
if ("$not" in match) {
negate = true;
match = match.$not;
}
if (match instanceof RegExp) {
result = !!value.toString().match(match);
} else if ("$nin" in match) {
result = match.$nin.indexOf(value) === -1;
} else {
return false;
}
}
return negate ? !result : result;
});
}
/* Match a mongoose query operator to a document */
function matchQueryOperator(operator, doc) {
if ("$or" in operator) {
return operator.$or.some(function(op) {
return matchQueryOperator(op, doc);
});
} else if ("$and" in operator) {
return operator.$and.every(function(op) {
return matchQueryOperator(op, doc);
});
} else if ("$not" in operator) {
return !matchQueryOperator(operator.$not, doc);
} else {
return matchQueryCriterion(operator, doc);
}
}
module.exports = {
create: createQueryOperator,
match: matchQueryOperator
};
\ No newline at end of file
/*jshint node:true*/
"use strict";
/*!
* Root handlers
*/
var nativeGet = nativePropertyGet;
var nativePost = nativePropertyPost;
/*!
* Property handlers
*/
function nativePropertyHook(req, next) {
var path = req.params["*"].split("/");
var obj = req.nativeRoot;
var parent;
var property;
while (path.length) {
property = path.shift();
if (!(property in obj)) {
delete req.nativeTarget;
return next();
}
parent = obj;
obj = obj[property];
}
req.nativeParent = parent;
req.nativeProperty = property;
req.nativeTarget = obj;
next();
}
function nativeArrayCount(req, cb) {
if (!("nativeTarget" in req)) {
return cb.notFound();
}
cb(null, req.nativeTarget.length);
}
function nativeArrayList(req, offset, limit, cb) {
if (!("nativeTarget" in req)) {
return cb.notFound();
}
var target = req.nativeTarget;
var arr;
if (limit > 0) {
arr = target.slice(offset, offset + limit);
} else {
arr = target.slice(offset);
}
cb(null, arr);
}
function nativeObjectCount(req, cb) {
cb(null, Object.keys(req.nativeTarget).length);
}
function nativeObjectList(req, offset, limit, cb) {
var keys = Object.keys(req.nativeTarget);
if (limit > 0) {
keys = keys.slice(offset, offset + limit);
} else {
keys = keys.slice(offset);
}
cb(null, keys);
}
function nativePropertyGet(req, cb) {
if (!("nativeTarget" in req)) {
return cb.notFound;
}
var target = req.nativeTarget;
if (Array.isArray(target)) {
if (req.options.rawArrays) {
cb(null, target);
} else {
cb.list(nativeArrayCount, nativeArrayList);
}
} else if (typeof target === "object") {
if (req.options.objectCollections) {
cb.list(nativeObjectCount, nativeObjectList);
} else {
cb(null, target);
}
} else {
cb(null, target);
}
}
function nativePropertyPut(req, isPatch, cb) {
var parent = req.nativeParent;
var property = req.nativeProperty;
var target = req.nativeTarget;
var value = req.body;
if ("_value" in value) {
value = value._value;
}
if (isPatch) {
if (typeof target !== "object") {
return cb.methodNotAllowed();
}
Object.keys(req.body).forEach(function(key) {
target[key] = value[key];
});
} else {
parent[property] = value;
}
cb();
}
function nativePropertyPost(req, cb) {
if (!("nativeTarget" in req)) {
return cb.notFound();
}
var target = req.nativeTarget;
var created;
if (Array.isArray(target)) {
var value = req.body;
if ("_value" in value) {
value = value._value;
}
created = value;
target.push(value);
} else if (typeof target === "object") {
if (!("_key" in req.body) || !("_value" in req.body) || typeof req.body._key !== "string") {
return cb.badRequest();
}
created = target[req.body._key] = req.body._value;
} else {
cb.methodNotAllowed();
}
if (req.options.postResponse) {
cb(null, created);
} else {
cb.created();
}
}
function nativePropertyDelete(req, cb) {
if (!("nativeTarget" in req)) {
return cb.notFound();
}
var parent = req.nativeParent;
var property = req.nativeProperty;
if (Array.isArray(parent) && !req.options.sparseArrays) {
parent.splice(property, 1);
} else {
delete parent[property];
}
cb();
}
/*!
* Native resource definition helper
*/
module.exports = function(name, obj) {
var resource = this.sub(name);
resource
.hook(function nativeHook(req, next) {
req.nativeRoot = req.nativeTarget = obj;
next();
})
.get(nativeGet)
.post(nativePost);
resource.sub("*")
.hook(nativePropertyHook)
.get(nativePropertyGet)
.put(nativePropertyPut)
.post(nativePropertyPost)
.del(nativePropertyDelete);
return resource;
};
/*jshint node:true*/
"use strict";
var utils = require("./utils");
var regexpSlashes = /\//g,
regexpTrimSlashes = /^\/|\/$/g,
regexpTrailingStar = /\*$/,
regexpAllNamedParameters = /:[^\/]+/g;
/*!
* Generic helpers
*/
var compiledCache = {};
function compilePattern(pattern, matchSubPaths) {
var cacheKey = matchSubPaths ? pattern + "[/*]" : pattern;
if (!(cacheKey in compiledCache)) {
var compiled = {
raw: pattern,
key: cacheKey
};
var regexp = "^\\/" + pattern
.replace(regexpSlashes, "\\/")
.replace(regexpAllNamedParameters, "([^\\/]+)")
.replace(regexpTrailingStar, "(.*)$");
compiled.trailingStar = !!(pattern.match(regexpTrailingStar));
if (!compiled.trailingStar && matchSubPaths) {
compiled.regexp = new RegExp(regexp + "(\\/.*)?$");
} else {
compiled.regexp = new RegExp(compiled.trailingStar ? regexp : (regexp + "$"));
}
compiled.names = (pattern.match(regexpAllNamedParameters) || []).map(function(name) {
return name.substr(1);
});
if (compiled.trailingStar) {
compiled.names.push("*");
}
compiledCache[cacheKey] = compiled;
}
return compiledCache[cacheKey];
}
function addHandler(handlers, compiled, method, handler) {
var item = Object.create(compiled);
item.method = method;
item.handler = handler;
handlers.push(item);
return item;
}
function addHook(handlers, compiled, hook, strict) {
if (!strict && !compiled.trailingStar) {
compiled = compilePattern(compiled.raw, true);
}
var item = Object.create(compiled);
item.hook = hook;
handlers.push(item);
return item;
}
function addOptions(handlers, compiled, options) {
var item = Object.create(compiled);
item.options = options;
handlers.push(item);
return item;
}
/*!
* Path matcher
*/
function Path(root, pattern) {
this.root = root;
this.compiled = compilePattern(pattern);
if (this.compiled.trailingStar) {
this.sub = undefined;
this.remove = undefined;
}
}
"get list count post put del".split(" ").forEach(function(method) {
Path.prototype[method] = function(handler) {
addHandler(this.root.handlers, this.compiled, method, handler);
return this;
};
});
Path.prototype.hook = function(hook, strict) {
addHook(this.root.handlers, this.compiled, hook, strict);
return this;
};
Path.prototype.readonly = function(subsToo) {
var path = this;
"post put del".split(" ").forEach(function(method) {
path[method](undefined);
if (subsToo) {
path.sub("*")[method](undefined);
}
});
return this;
};
Path.prototype.sub = function(pattern, hook) {
pattern = pattern.replace(regexpTrimSlashes, "");
return this.root.sub(this.compiled.raw + "/" + pattern, hook);
};
Path.prototype.remove = function(pattern) {
pattern = pattern.replace(regexpTrimSlashes, "");
return this.root.remove(this.compiled.raw + "/" + pattern);
};
Path.prototype.set = function(key, value, strict) {
if (typeof key === "object") {
strict = value;
}
var compiled = this.compiled;
if (!strict && !compiled.trailingStar) {
compiled = compilePattern(compiled.raw, true);
}
var hook = this.root.handlers.filter(function(h) {
return h.options && h.raw === compiled.raw;
})[0];
if (!hook) {
hook = addOptions(this.root.handlers, compiled, {});
}
if (typeof key === "object") {
Object.keys(key).forEach(function(k) {
hook.options[k] = key[k];
});
} else {
hook.options[key] = value;
}
return this;
};
/*!
* Base hooks
*/
function getParamHook(names, match) {
// Strip full match
var values = match.slice(1);
return function paramHook(req, next) {
req.params = req.params || {};
names.forEach(function(name) {
var value = values.shift();
if (name === "*") {
req.params[name] = value;
} else {
req.params[name] = decodeURIComponent(value);
}
});
next();
};
}
function getOptionsHook() {
var hook = function optionsHook(req, next) {
req.options = req.options || {};
Object.keys(hook.options).forEach(function(key) {
req.options[key] = hook.options[key];
});
next();
};
hook.options = {};
return hook;
}
function getHref(subpath) {
/*jshint validthis:true */
var req = this;
var path = req.path.replace(regexpTrimSlashes, "");
if (subpath) {
path = path + "/" + subpath.replace(regexpTrimSlashes, "");
}
return utils.getHref(req, path);
}
function matchPattern(pattern, path) {
/*jshint validthis:true */
var compiled = compilePattern(pattern);
var req = this;
var match = (path || req.path).match(compiled.regexp);
if (match) {
var values = match.slice(1);
var params = {};
compiled.names.forEach(function(name) {
params[name] = values.shift();
});
return params;
}
return false;
}
var defaultHooks = [
/* Add request helpers */
function requestHelpersHook(req, next) {
req.getHref = getHref;
req.match = matchPattern;
next();
}
];
/*!
* Root resource
*/
function rootResource() {
var root = {
handlers: [],
sub: function(pattern, hook) {
var path = new Path(root, pattern.replace(regexpTrimSlashes, ""));
if (hook) {
path.hook(hook);
}
return path;
},
remove: function(pattern) {
pattern = pattern.replace(regexpTrimSlashes, "");
function filterFunc(h) {
var raw = h.raw;
if (raw.substr(0, pattern.length) === pattern) {
if (raw.length === pattern.length || raw[pattern.length] === "/") {
return false;
}
}
return true;
}
root.handlers = root.handlers.filter(filterFunc);
},
match: function(req) {
var matchingHooks = defaultHooks.slice(0);
var spec = {};
var matchedPatterns = [];
/* Add options hook */
var optionsHook = getOptionsHook();
matchingHooks.push(optionsHook);
/* Find handlers matching requested path */
root.handlers.forEach(function(h) {
var match = req.path.match(h.regexp);
if (match) {
/* Add parameter hook only once for each pattern */
if (h.names.length > 0 && matchedPatterns.indexOf(h.raw) === -1) {
matchingHooks.push(getParamHook(h.names, match));
matchedPatterns.push(h.raw);
}
if (h.options) {
var options = optionsHook.options;
Object.keys(h.options).forEach(function(key) {
options[key] = h.options[key];
});
}
if (h.hook) {
matchingHooks.push(h.hook);
}
if (h.method) {
// get and count/list override each other
if (h.method === "get") {
delete spec.count;
delete spec.list;
}
if (h.method === "count" || h.method === "list") {
delete spec.get;
}
spec[h.method] = h.handler;
}
}
});
if (Object.keys(spec).length) {
return { spec: spec, hooks: matchingHooks };
}
}
};
return root;
}
module.exports = rootResource;
/*jshint node:true */
"use strict";
var util = require("util");
function extractRoute(req) {
var url = req.url,
orig = req.originalUrl;
return orig.substring(0, orig.lastIndexOf(url));
}
var utils = {
getHref: function(req, urlpath) {
return util.format("%s://%s%s/%s",
req.protocol,
req.headers.host,
extractRoute(req),
urlpath
);
},
addHref: function(req, doc, urlpath) {
doc._href = this.getHref(req, urlpath);
}
};
module.exports = utils;
\ No newline at end of file
/*jshint node:true */
"use strict";
var rootResource = require("./root"),
httpStatus = require("./httpStatus");
function instanciate() {
var root = rootResource();
function yarm(options) {
options = options || {};
options.defaultLimit = options.defaultLimit || 10;
options.errorStack = options.errorStack || false;
function handleError(req, res, err) {
if (err) {
res.status(err.code || 500)
.send(options.errorStack ? err.stack : err.message);
return true;
}
}
function makeCallback(req, res) {
function cb(err, body, mime) {
if (!handleError(req, res, err)) {
sendResponse(req, res, body, mime);
}
}
cb.file = function(err, path, mime) {
if (!handleError(req, res, err)) {
sendFile(req, res, path, mime);
}
};
cb.status = function(code, body) {
handleError(req, res, httpStatus(code, body));
};
cb.list = function(counter, lister) {
sendListResponse(req, res, counter, lister);
};
cb.custom = function(handler) {
handler(req, res);
};
httpStatus.names.forEach(function(name) {
cb[name] = function() {
handleError(req, res, httpStatus[name]());
};
});
return cb;
}
function sendFile(req, res, path, mime) {
if (mime) {
res.type(mime);
}
res.sendFile(path);
}
function sendResponse(req, res, body, mime) {
if (body === null || body === undefined) {
handleError(req, res, httpStatus.noContent());
return;
}
if (mime) {
res.type(mime);
}
if (typeof body === "number") {
// Cast to string to avoid mistaking body for HTTP status
body = "" + body;
}
// TODO look for a cleaner way to identify Readables
if (body && typeof body._read === "function") {
body.pipe(res);
} else if (body) {
res.send(body);
}
}
function sendListResponse(req, res, counter, lister) {
var skip = parseInt(req.query["skip"], 10),
limit = parseInt(req.query["limit"], 10);
if (isNaN(skip)) {
skip = 0;
}
if (isNaN(limit)) {
limit = options.defaultLimit;
}
counter(req, function(err, count) {
if (handleError(req, res, err)) {
return;
}
lister(req, skip, limit, function(err, items) {
if (handleError(req, res, err)) {
return;
}
res.send({
_count: count,
_items: items
});
});
});
}
function restResult(req, res, handlers) {
var method = req.method.toUpperCase();
switch(method) {
case "GET":
case "HEAD":
if (handlers.get) {
return handlers.get(req, makeCallback(req, res));
} else if (handlers.count && handlers.list) {
return sendListResponse(req, res, handlers.count, handlers.list);
}
break;
case "PUT":
case "PATCH":
if (handlers.put) {
return handlers.put(req, method === "PATCH", makeCallback(req, res));
}
break;
case "DELETE":
if (handlers.del) {
return handlers.del(req, makeCallback(req, res));
}
break;
case "POST":
if (handlers.post) {
return handlers.post(req, makeCallback(req, res));
}
break;
}
(makeCallback(req, res)).methodNotAllowed();
}
return function(req, res) {
var data = root.match(req);
function nextHook(err) {
setImmediate(function() {
if (err) {
handleError(req, res, err);
return;
}
var hook = data.hooks.shift();
if (hook) {
try {
hook.call(null, req, nextHook);
} catch(e) {
nextHook(e);
}
} else {
restResult(req, res, data.spec);
}
});
}
nextHook.status = function(code, body) {
handleError(req, res, httpStatus(code, body));
};
httpStatus.names.forEach(function(name) {
nextHook[name] = function() {
nextHook(httpStatus[name]());
};
});
if (data && data.spec) {
data.hooks = data.hooks || [];
nextHook();
} else {
handleError(req, res, httpStatus.notFound());
}
};
}
/* Resource definers */
yarm.resource = function(name) {
return root.sub(name);
};
yarm.remove = function(name) {
root.remove(name);
};
/* Extension helper */
yarm.extend = function(name, handler) {
if (name in yarm) {
throw new Error("Yarm extension '" + name + "' is already defined");
}
yarm[name] = handler.bind(root);
};
return yarm;
}
module.exports = instanciate;
{
"_from": "yarm",
"_id": "yarm@0.4.0",
"_inBundle": false,
"_integrity": "sha1-PZpzq+5JDs53yI670JKf6m9ykHs=",
"_location": "/yarm",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "yarm",
"name": "yarm",
"escapedName": "yarm",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER",
"/"
],
"_resolved": "https://registry.npmjs.org/yarm/-/yarm-0.4.0.tgz",
"_shasum": "3d9a73abee490ece77c88ebbd0929fea6f72907b",
"_spec": "yarm",
"_where": "/home/lab1-24/Desktop/nodejs/student",
"author": {
"name": "Nicolas Joyard",
"email": "joyard.nicolas@gmail.com"
},
"bugs": {
"url": "https://github.com/njoyard/yarm/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "Yet Another REST Middleware for express and mongoose",
"devDependencies": {
"body-parser": "*",
"express": "4.*",
"mocha": "2.*",
"mongodb": "2.*",
"mongoose": "4.*"
},
"homepage": "http://yarm.njoyard.fr",
"keywords": [
"rest",
"restful",
"express",
"mongoose"
],
"license": "MIT",
"main": "index.js",
"name": "yarm",
"repository": {
"type": "git",
"url": "git://github.com/njoyard/yarm.git"
},
"scripts": {
"test": "mocha"
},
"version": "0.4.0"
}
/*jshint node:true */
/*global describe, it */
"use strict";
var assert = require("assert"),
common = require("./common"),
resource = common.resource,
request = common.request,
callbackTests = common.callbackTests;
describe("All resources", function() {
describe("PUT", function() {
callbackTests("PUT", it);
it("should pass false as a second argument to .put", function(done) {
var value;
resource("test").put(function(req, isPatch, cb) {
value = isPatch;
cb();
});
request.put("/test", function(res, body) {
assert.strictEqual(false, value);
done();
});
});
});
describe("PATCH", function() {
callbackTests("PATCH", it);
it("should pass true as a second argument to .put", function(done) {
var value;
resource("test").put(function(req, isPatch, cb) {
value = isPatch;
cb();
});
request.patch("/test", function(res, body) {
assert.strictEqual(true, value);
done();
});
});
});
describe("POST", function() {
callbackTests("POST", it);
});
describe("DELETE", function() {
callbackTests("DELETE", it);
});
});
describe("Document resources", function() {
describe("GET", function() {
callbackTests("GET", it);
});
});
describe("Collection resources", function() {
describe("GET", function() {
callbackTests("COUNT", it);
callbackTests("LIST", it);
it("should send a JSON response with the results from .count and .list", function(done) {
resource("test")
.count(function(req, cb) {
cb(null, 42);
})
.list(function(req, offset, limit, cb) {
cb(null, ["foo", "bar"]);
});
request.get("/test", function(res, body) {
var jbody;
assert.doesNotThrow(function() { jbody = JSON.parse(body); });
assert.deepEqual({
_count: 42,
_items: ["foo", "bar"]
}, jbody);
done();
});
});
});
});
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*jshint node:true */
/*global describe, it */
"use strict";
var assert = require("assert"),
common = require("./common"),
yarm = require("../index.js"),
request = common.request;
describe("New instance", function() {
it("Should enable creating new yarm instances", function() {
assert.strictEqual(typeof yarm.newInstance, "function");
var instance = yarm.newInstance();
assert.strictEqual(typeof instance, "function");
assert.strictEqual(typeof instance.resource, "function");
});
it("Should not share resources between separate instances", function(done) {
var instance = yarm.newInstance();
common.app.use("/rest2", instance());
yarm.resource("test1")
.get(function(req, cb) {
cb(null, "Test 1 resource");
});
instance.resource("test2")
.get(function(req, cb) {
cb(null, "Test 2 resource");
});
request.get("/test1", function(res, body) {
assert.strictEqual(body, "Test 1 resource");
request.get("/test2", function(res, body) {
assert.strictEqual(res.statusCode, 404);
request.get("2/test1", function(res, body) {
assert.strictEqual(res.statusCode, 404);
request.get("2/test2", function(res, body) {
assert.strictEqual(body, "Test 2 resource");
done();
});
});
});
});
});
});
This diff is collapsed.
Test file content
\ No newline at end of file
{
"name": "student",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"yarm": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/yarm/-/yarm-0.4.0.tgz",
"integrity": "sha1-PZpzq+5JDs53yI670JKf6m9ykHs="
}
}
}
{
"name": "student",
"version": "1.0.0",
"description": "\"display information given for partially entered Patiphan Marak\"",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "http://projectcs.sci.ubu.ac.th/Patiphan/node-js-60-2.git"
},
"author": "csubu",
"license": "ISC",
"dependencies": {
"yarm": "^0.4.0"
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment