Fixed Non-ASCII attachment filename will crash when downloading.

Thanks to xet7 !

Fixes #2759
This commit is contained in:
Lauri Ojansivu 2021-04-29 13:26:49 +03:00
parent 843ff8eaaa
commit c2da477735
277 changed files with 30568 additions and 52 deletions

View file

@ -0,0 +1,18 @@
# .editorconfig
# Meteor adapted EditorConfig, http://EditorConfig.org
# By RaiX 2013
root = true
[*.js]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
charset = utf-8
max_line_length = 80
indent_brace_style = 1TBS
spaces_around_operators = true
quote_type = auto
# curly_bracket_next_line = true

View file

@ -0,0 +1,4 @@
/versions.json
.build*
smart.lock
/nbproject/

View file

@ -0,0 +1,97 @@
{
// JSHint Meteor Configuration File
// Match the Meteor Style Guide
//
// By @raix with contributions from @aldeed and @awatson1978
// Source https://github.com/raix/Meteor-jshintrc
//
// See http://jshint.com/docs/ for more details
"maxerr": 50,
"bitwise": true,
"camelcase": true,
"curly": true,
"eqeqeq": true,
"forin": true,
"immed": false,
"indent": 2,
"latedef": false,
"newcap": false,
"noarg": true,
"noempty": true,
"nonew": false,
"plusplus": false,
"quotmark": false,
"undef": true,
"unused": true,
"strict": true,
"trailing": true,
"maxparams": false,
"maxdepth": false,
"maxstatements": false,
"maxcomplexity": false,
"maxlen": 80,
"asi": false,
"boss": false,
"debug": false,
"eqnull": false,
"es5": false,
"esnext": false,
"moz": false,
"evil": false,
"expr": false,
"funcscope": false,
"globalstrict": true,
"iterator": false,
"lastsemic": false,
"laxbreak": false,
"laxcomma": false,
"loopfunc": false,
"multistr": false,
"proto": false,
"scripturl": false,
"smarttabs": false,
"shadow": false,
"sub": false,
"supernew": false,
"validthis": false,
"browser": true,
"couch": false,
"devel": true,
"dojo": false,
"jquery": false,
"mootools": false,
"node": false,
"nonstandard": false,
"prototypejs": false,
"rhino": false,
"worker": false,
"wsh": false,
"yui": false,
"nomen": false,
"onevar": false,
"passfail": false,
"white": false,
"predef": [
"Meteor",
"Accounts",
"Session",
"Template",
"check",
"Match",
"Deps",
"EJSON",
"Email",
"Package",
"Tinytest",
"Npm",
"Assets",
"Packages",
"process",
"LocalCollection",
"_",
"Random",
"HTTP",
"_methodHTTP"
]
}

View file

@ -0,0 +1,5 @@
language: node_js
node_js:
- "0.10"
before_install:
- "curl -L http://git.io/s0Zu-w | /bin/sh"

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 [@raix](https://github.com/raix), aka Morten N.O. Nørgaard Henriksen, mh@gi-software.com
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.

View file

@ -0,0 +1,129 @@
wekan-cfs-http-publish [![Build Status](https://travis-ci.org/CollectionFS/Meteor-http-publish.png?branch=master)](https://travis-ci.org/CollectionFS/Meteor-http-publish)
============
This package add the ability to add `HTTP` server publish to your project. It's a server-side package only.
DEPRECATING: Use https://atmospherejs.com/simple/rest instead
## Usage
HTTP.publish creates a http crud restpoint for a collection *- only one cursor is allowed pr. publish*
### Security
`CRUD+L` - Create Read Update Delete + List are common rest point operations.
All `CUD` methods are the exact same as the `ddp` methods handlers - This means that `Meteor.allow` and `Meteor.deny` are setting the access rules for both `ddp` and `http` collection methods.
All `R+L` methods are limited to the publish function.
### Fully mounted
If handed a collection and a publish function the HTTP.publish will mount on follow urls and methods:
* `GET` - `/api/list` *- all published data*
* `POST` - `/api/list` *- insert a document into collection*
* `GET` - `/api/list/:id` *- find one published document*
* `PUT` - `/api/list/:id` *- update a document*
* `DELETE` - `/api/list/:id` *- remove a document*
```js
myCollection = new Meteor.Collection('list');
// Add access points for `GET`, `POST`, `PUT`, `DELETE`
HTTP.publish({collection: myCollection}, function(data) {
// this.userId, this.query, this.params
return myCollection.find({});
});
```
### Publish view only
If handed a mount name and a publish function the HTTP.publish will mount:
* `GET` - `/mylist` *- all published data*
```js
myCollection = new Meteor.Collection('list');
// Add access points for `GET`
HTTP.publish({name: 'mylist'}, function(data) {
// this.userId, this.query, this.params
return myCollection.find({});
});
```
### Create Update Delete only
If handed a collection only the HTTP.publish will mount:
* `POST` - `/api/list` *- insert a document into collection*
* `PUT` - `/api/list/:id` *- update a document*
* `DELETE` - `/api/list/:id` *- remove a document*
```js
myCollection = new Meteor.Collection('list');
// Add access points for `POST`, `PUT`, `DELETE`
HTTP.publish({collection: myCollection});
```
## Publish scope
The publish scope contains different kinds of inputs. We can also get user details if logged in.
* `this.userId` The user whos id and token was used to run this method, if set/found
* `this.query` - query params `?token=1` -> { token: 1 }
* `this.params` - Set params /foo/:name/test/:id -> { name: '', id: '' }
## Passing data via header
From the client:
```js
HTTP.get('/api/list', {
data: { foo: 'bar' }
}, function(err, result) {
console.log('Content in parsed json: ');
console.log(result.data);
});
```
HTTP Server method:
```js
HTTP.publish({collection: myCollection}, function(data) {
// data === { foo: 'bar' }
});
```
## Authentication
For details on authentication of http calls please read the [Authentication part in HTTP.methods package](https://github.com/raix/Meteor-http-methods#authentication)
*The publish will have the `this.userId` set if an authenticated user is making the request.*
## Format handlers
The query parametre `format` is used to set different output formats. The buildin format is `json` *(EJSON since we are on Meteor)*
Example: *(`json` is buildin)*
```js
// Format the output into json
HTTP.publishFormats({
'json': function(result) {
// Set the method scope content type to json
this.setContentType('application/json');
// Return EJSON string
return EJSON.stringify(result);
}
});
```
`GET` url: `/api/list?format=json`
```js
HTTP.get('/api/list', {
params: {
format: 'json'
}
}, function(err, result) {
console.log('Back from update');
if (err) {
console.log('Got error');
}
console.log('Got json back: ' + result.content);
});
```
## Unpublish
For `api` integrity theres added an `HTTP.unpublish` method that takes a collection or name of mount point to remove.
## API Documentation
[Here](api.md)

View file

@ -0,0 +1,153 @@
## http-publish Public API ##
Adds HTTP.publish and HTTP.unpublish RESTful
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
-
### <a name="_publishHTTP"></a>_publishHTTP {any}&nbsp;&nbsp;<sub><i>Server</i></sub> ###
```
GET /note
GET /note/:id
POST /note
PUT /note/:id
DELETE /note/:id
```
> ```_publishHTTP = { ...``` [http.publish.server.api.js:20](http.publish.server.api.js#L20)
-
Could be cool if we could serve some api doc or even an api script
user could do <script href="/note/api?token=1&user=2"></script> and be served
a client-side javascript api?
Eg.
HTTP.api.note.create();
HTTP.api.login(username, password);
HTTP.api.logout
-
### <a name="HTTP.publishFormats"></a>*http*.publishFormats(newHandlers)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __publishFormats__ is defined in `HTTP`*
__Arguments__
* __newHandlers__ *{Object}*
__Returns__ *{undefined}*
Add publish formats. Example:
```js
HTTP.publishFormats({
json: function(inputObject) {
// Set the method scope content type to json
this.setContentType('application/json');
// Return EJSON string
return EJSON.stringify(inputObject);
}
});
```
> ```HTTP.publishFormats = function httpPublishFormats(newHandlers) { ...``` [http.publish.server.api.js:215](http.publish.server.api.js#L215)
-
### <a name="HTTP.publish"></a>*http*.publish(options, [name], [collection], [publishFunc])&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __publish__ is defined in `HTTP`*
__Arguments__
* __options__ *{Object}*
* __defaultFormat__ *{String}* (Optional, Default = 'json')
Format to use for responses when `format` is not found in the query string.
* __collectionGet__ *{String}* (Optional, Default = true)
Add GET restpoint for collection? Requires a publish function.
* __collectionPost__ *{String}* (Optional, Default = true)
Add POST restpoint for adding documents to the collection?
* __documentGet__ *{String}* (Optional, Default = true)
Add GET restpoint for documents in collection? Requires a publish function.
* __documentPut__ *{String}* (Optional, Default = true)
Add PUT restpoint for updating a document in the collection?
* __documentDelete__ *{String}* (Optional, Default = true)
Add DELETE restpoint for deleting a document in the collection?
* __name__ *{String}* (Optional)
Restpoint name (url prefix). Optional if `collection` is passed. Will mount on `/api/collectionName` by default.
* __collection__ *{[Meteor.Collection](#Meteor.Collection)}* (Optional)
Meteor.Collection instance. Required for all restpoints except collectionGet
* __publishFunc__ *{Function}* (Optional)
A publish function. Required to mount GET restpoints.
__Returns__ *{undefined}*
Publishes one or more restpoints, mounted on "name" ("/api/collectionName/"
by default). The GET restpoints are subscribed to the document set (cursor)
returned by the publish function you supply. The other restpoints forward
requests to Meteor's built-in DDP methods (insert, update, remove), meaning
that full allow/deny security is automatic.
__Usage:__
Publish only:
HTTP.publish({name: 'mypublish'}, publishFunc);
Publish and mount crud rest point for collection /api/myCollection:
HTTP.publish({collection: myCollection}, publishFunc);
Mount CUD rest point for collection and documents without GET:
HTTP.publish({collection: myCollection});
> ```HTTP.publish = function httpPublish(options, publishFunc) { ...``` [http.publish.server.api.js:256](http.publish.server.api.js#L256)
-
### <a name="HTTP.unpublish"></a>*http*.unpublish([name])&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __unpublish__ is defined in `HTTP`*
__Arguments__
* __name__ *{String|[Meteor.Collection](#Meteor.Collection)}* (Optional)
The method name or collection
__Returns__ *{undefined}*
Unpublishes all HTTP methods that were published with the given name or
for the given collection. Call with no arguments to unpublish all.
> ```HTTP.unpublish = _publishHTTP.unpublish;``` [http.publish.server.api.js:453](http.publish.server.api.js#L453)

View file

@ -0,0 +1,12 @@
// Client-side is not implemented
HTTP.publish = function() {
throw new Error('HTTP.publish not implemented on client-side');
};
HTTP.publishFormats = function() {
throw new Error('HTTP.publishFormats not implemented on client-side');
};
HTTP.unpublish = function() {
throw new Error('HTTP.unpublish not implemented on client-side');
};

View file

@ -0,0 +1,466 @@
/*
GET /note
GET /note/:id
POST /note
PUT /note/:id
DELETE /note/:id
*/
// Could be cool if we could serve some api doc or even an api script
// user could do <script href="/note/api?token=1&user=2"></script> and be served
// a client-side javascript api?
// Eg.
// HTTP.api.note.create();
// HTTP.api.login(username, password);
// HTTP.api.logout
_publishHTTP = {};
// Cache the names of all http methods we've published
_publishHTTP.currentlyPublished = [];
var defaultAPIPrefix = '/api/';
/**
* @method _publishHTTP.getPublishScope
* @private
* @param {Object} scope
* @returns {httpPublishGetPublishScope.publishScope}
*
* Creates a nice scope for the publish method
*/
_publishHTTP.getPublishScope = function httpPublishGetPublishScope(scope) {
var publishScope = {};
publishScope.userId = scope.userId;
publishScope.params = scope.params;
publishScope.query = scope.query;
// TODO: Additional scoping
// publishScope.added
// publishScope.ready
return publishScope;
};
_publishHTTP.formatHandlers = {};
/**
* @method _publishHTTP.formatHandlers.json
* @private
* @param {Object} result - The result object
* @returns {String} JSON
*
* Formats the output into JSON and sets the appropriate content type on `this`
*/
_publishHTTP.formatHandlers.json = function httpPublishJSONFormatHandler(result) {
// Set the method scope content type to json
this.setContentType('application/json');
// Return EJSON string
return EJSON.stringify(result);
};
/**
* @method _publishHTTP.formatResult
* @private
* @param {Object} result - The result object
* @param {Object} scope
* @param {String} [defaultFormat='json'] - Default format to use if format is not in query string.
* @returns {Any} The formatted result
*
* Formats the result into the format selected by querystring eg. "&format=json"
*/
_publishHTTP.formatResult = function httpPublishFormatResult(result, scope, defaultFormat) {
// Get the format in lower case and default to json
var format = scope && scope.query && scope.query.format || defaultFormat || 'json';
// Set the format handler found
var formatHandlerFound = !!(typeof _publishHTTP.formatHandlers[format] === 'function');
// Set the format handler and fallback to default json if handler not found
var formatHandler = _publishHTTP.formatHandlers[(formatHandlerFound) ? format : 'json'];
// Check if format handler is a function
if (typeof formatHandler !== 'function') {
// We break things the user could have overwritten the default json handler
throw new Error('The default json format handler not found');
}
if (!formatHandlerFound) {
scope.setStatusCode(500);
return '{"error":"Format handler for: `' + format + '` not found"}';
}
// Execute the format handler
try {
return formatHandler.apply(scope, [result]);
} catch(err) {
scope.setStatusCode(500);
return '{"error":"Format handler for: `' + format + '` Error: ' + err.message + '"}';
}
};
/**
* @method _publishHTTP.error
* @private
* @param {String} statusCode - The status code
* @param {String} message - The message
* @param {Object} scope
* @returns {Any} The formatted result
*
* Responds with error message in the expected format
*/
_publishHTTP.error = function httpPublishError(statusCode, message, scope) {
var result = _publishHTTP.formatResult(message, scope);
scope.setStatusCode(statusCode);
return result;
};
/**
* @method _publishHTTP.getMethodHandler
* @private
* @param {Meteor.Collection} collection - The Meteor.Collection instance
* @param {String} methodName - The method name
* @returns {Function} The server method
*
* Returns the DDP connection handler, already setup and secured
*/
_publishHTTP.getMethodHandler = function httpPublishGetMethodHandler(collection, methodName) {
if (collection instanceof Meteor.Collection) {
if (collection._connection && collection._connection.method_handlers) {
return collection._connection.method_handlers[collection._prefix + methodName];
} else {
throw new Error('HTTP publish does not work with current version of Meteor');
}
} else {
throw new Error('_publishHTTP.getMethodHandler expected a collection');
}
};
/**
* @method _publishHTTP.unpublishList
* @private
* @param {Array} names - List of method names to unpublish
* @returns {undefined}
*
* Unpublishes all HTTP methods that have names matching the given list.
*/
_publishHTTP.unpublishList = function httpPublishUnpublishList(names) {
if (!names.length) {
return;
}
// Carry object for methods
var methods = {};
// Unpublish the rest points by setting them to false
for (var i = 0, ln = names.length; i < ln; i++) {
methods[names[i]] = false;
}
HTTP.methods(methods);
// Remove the names from our list of currently published methods
_publishHTTP.currentlyPublished = _.difference(_publishHTTP.currentlyPublished, names);
};
/**
* @method _publishHTTP.unpublish
* @private
* @param {String|Meteor.Collection} [name] - The method name or collection
* @returns {undefined}
*
* Unpublishes all HTTP methods that were published with the given name or
* for the given collection. Call with no arguments to unpublish all.
*/
_publishHTTP.unpublish = function httpPublishUnpublish(/* name or collection, options */) {
// Determine what method name we're unpublishing
var name = (arguments[0] instanceof Meteor.Collection) ?
defaultAPIPrefix + arguments[0]._name : arguments[0];
// Unpublish name and name/id
if (name && name.length) {
_publishHTTP.unpublishList([name, name + '/:id']);
}
// If no args, unpublish all
else {
_publishHTTP.unpublishList(_publishHTTP.currentlyPublished);
}
};
/**
* @method HTTP.publishFormats
* @public
* @param {Object} newHandlers
* @returns {undefined}
*
* Add publish formats. Example:
```js
HTTP.publishFormats({
json: function(inputObject) {
// Set the method scope content type to json
this.setContentType('application/json');
// Return EJSON string
return EJSON.stringify(inputObject);
}
});
```
*/
HTTP.publishFormats = function httpPublishFormats(newHandlers) {
_.extend(_publishHTTP.formatHandlers, newHandlers);
};
/**
* @method HTTP.publish
* @public
* @param {Object} options
* @param {String} [name] - Restpoint name (url prefix). Optional if `collection` is passed. Will mount on `/api/collectionName` by default.
* @param {Meteor.Collection} [collection] - Meteor.Collection instance. Required for all restpoints except collectionGet
* @param {String} [options.defaultFormat='json'] - Format to use for responses when `format` is not found in the query string.
* @param {String} [options.collectionGet=true] - Add GET restpoint for collection? Requires a publish function.
* @param {String} [options.collectionPost=true] - Add POST restpoint for adding documents to the collection?
* @param {String} [options.documentGet=true] - Add GET restpoint for documents in collection? Requires a publish function.
* @param {String} [options.documentPut=true] - Add PUT restpoint for updating a document in the collection?
* @param {String} [options.documentDelete=true] - Add DELETE restpoint for deleting a document in the collection?
* @param {Function} [publishFunc] - A publish function. Required to mount GET restpoints.
* @returns {undefined}
* @todo this should use options argument instead of optional args
*
* Publishes one or more restpoints, mounted on "name" ("/api/collectionName/"
* by default). The GET restpoints are subscribed to the document set (cursor)
* returned by the publish function you supply. The other restpoints forward
* requests to Meteor's built-in DDP methods (insert, update, remove), meaning
* that full allow/deny security is automatic.
*
* __Usage:__
*
* Publish only:
*
* HTTP.publish({name: 'mypublish'}, publishFunc);
*
* Publish and mount crud rest point for collection /api/myCollection:
*
* HTTP.publish({collection: myCollection}, publishFunc);
*
* Mount CUD rest point for collection and documents without GET:
*
* HTTP.publish({collection: myCollection});
*
*/
HTTP.publish = function httpPublish(options, publishFunc) {
options = _.extend({
name: null,
auth: null,
collection: null,
defaultFormat: null,
collectionGet: true,
collectionPost: true,
documentGet: true,
documentPut: true,
documentDelete: true
}, options || {});
var collection = options.collection;
// Use provided name or build one
var name = (typeof options.name === "string") ? options.name : defaultAPIPrefix + collection._name;
// Make sure we have a name
if (typeof name !== "string") {
throw new Error('HTTP.publish expected a collection or name option');
}
var defaultFormat = options.defaultFormat;
// Rig the methods for the CRUD interface
var methods = {};
// console.log('HTTP restpoint: ' + name);
// list and create
methods[name] = {};
if (options.collectionGet && publishFunc) {
// Return the published documents
methods[name].get = function(data) {
// Format the scope for the publish method
var publishScope = _publishHTTP.getPublishScope(this);
// Get the publish cursor
var cursor = publishFunc.apply(publishScope, [data]);
// Check if its a cursor
if (cursor && cursor.fetch) {
// Fetch the data fron cursor
var result = cursor.fetch();
// Return the data
return _publishHTTP.formatResult(result, this, defaultFormat);
} else {
// We didnt get any
return _publishHTTP.error(200, [], this);
}
};
}
if (collection) {
// If we have a collection then add insert method
if (options.collectionPost) {
methods[name].post = function(data) {
var insertMethodHandler = _publishHTTP.getMethodHandler(collection, 'insert');
// Make sure that _id isset else create a Meteor id
data._id = data._id || Random.id();
// Create the document
try {
// We should be passed a document in data
insertMethodHandler.apply(this, [data]);
// Return the data
return _publishHTTP.formatResult({ _id: data._id }, this, defaultFormat);
} catch(err) {
// This would be a Meteor.error?
return _publishHTTP.error(err.error, { error: err.message }, this);
}
};
}
// We also add the findOne, update and remove methods
methods[name + '/:id'] = {};
if (options.documentGet && publishFunc) {
// We have to have a publish method inorder to publish id? The user could
// just write a publish all if needed - better to make this explicit
methods[name + '/:id'].get = function(data) {
// Get the mongoId
var mongoId = this.params.id;
// We would allways expect a string but it could be empty
if (mongoId !== '') {
// Format the scope for the publish method
var publishScope = _publishHTTP.getPublishScope(this);
// Get the publish cursor
var cursor = publishFunc.apply(publishScope, [data]);
// Result will contain the document if found
var result;
// Check to see if document is in published cursor
if (cursor) {
cursor.forEach(function(doc) {
if (!result) {
if (doc._id === mongoId) {
result = doc;
}
}
});
}
// If the document is found the return
if (result) {
return _publishHTTP.formatResult(result, this, defaultFormat);
} else {
// We do a check to see if the doc id exists
var exists = collection.findOne({ _id: mongoId });
// If it exists its not published to the user
if (exists) {
// Unauthorized
return _publishHTTP.error(401, { error: 'Unauthorized' }, this);
} else {
// Not found
return _publishHTTP.error(404, { error: 'Document with id ' + mongoId + ' not found' }, this);
}
}
} else {
return _publishHTTP.error(400, { error: 'Method expected a document id' }, this);
}
};
}
if (options.documentPut) {
methods[name + '/:id'].put = function(data) {
// Get the mongoId
var mongoId = this.params.id;
// We would allways expect a string but it could be empty
if (mongoId !== '') {
var updateMethodHandler = _publishHTTP.getMethodHandler(collection, 'update');
// Create the document
try {
// We should be passed a document in data
updateMethodHandler.apply(this, [{ _id: mongoId }, data]);
// Return the data
return _publishHTTP.formatResult({ _id: mongoId }, this, defaultFormat);
} catch(err) {
// This would be a Meteor.error?
return _publishHTTP.error(err.error, { error: err.message }, this);
}
} else {
return _publishHTTP.error(400, { error: 'Method expected a document id' }, this);
}
};
}
if (options.documentDelete) {
methods[name + '/:id'].delete = function(data) {
// Get the mongoId
var mongoId = this.params.id;
// We would allways expect a string but it could be empty
if (mongoId !== '') {
var removeMethodHandler = _publishHTTP.getMethodHandler(collection, 'remove');
// Create the document
try {
// We should be passed a document in data
removeMethodHandler.apply(this, [{ _id: mongoId }]);
// Return the data
return _publishHTTP.formatResult({ _id: mongoId }, this, defaultFormat);
} catch(err) {
// This would be a Meteor.error?
return _publishHTTP.error(err.error, { error: err.message }, this);
}
} else {
return _publishHTTP.error(400, { error: 'Method expected a document id' }, this);
}
};
}
}
// Authenticate with our own auth method: https://github.com/zcfs/Meteor-http-methods#authentication
if (options.auth) {
if (methods[name]) {
methods[name].auth = options.auth;
}
if (methods[name + '/:id']) {
methods[name + '/:id'].auth = options.auth;
}
}
// Publish the methods
HTTP.methods(methods);
// Mark these method names as currently published
_publishHTTP.currentlyPublished = _.union(_publishHTTP.currentlyPublished, _.keys(methods));
}; // EO Publish
/**
* @method HTTP.unpublish
* @public
* @param {String|Meteor.Collection} [name] - The method name or collection
* @returns {undefined}
*
* Unpublishes all HTTP methods that were published with the given name or
* for the given collection. Call with no arguments to unpublish all.
*/
HTTP.unpublish = _publishHTTP.unpublish;

View file

@ -0,0 +1,175 @@
function equals(a, b) {
return !!(EJSON.stringify(a) === EJSON.stringify(b));
}
list = new Meteor.Collection('list');
console.log('Client url: ' + Meteor.absoluteUrl('api'));
Tinytest.add('http-publish - client - test environment', function(test) {
test.isTrue(typeof _publishHTTP === 'undefined', 'test environment not initialized _publishHTTP');
test.isTrue(typeof HTTP !== 'undefined', 'test environment not initialized HTTP');
test.isTrue(typeof HTTP.publish !== 'undefined', 'test environment not initialized HTTP.publish');
test.isTrue(typeof HTTP.unpublish !== 'undefined', 'test environment not initialized HTTP.unpublish');
test.isTrue(typeof HTTP.publishFormats !== 'undefined', 'test environment not initialized HTTP.publishFormats');
});
Tinytest.addAsync('http-publish - client - clearTest', function (test, onComplete) {
test.isTrue(true);
Meteor.call('clearTest', function(err, result) {
test.isTrue(result);
onComplete();
});
test.isTrue(true);
});
id = '';
removedId = '';
Tinytest.addAsync('http-publish - client - get list', function (test, onComplete) {
HTTP.get(Meteor.absoluteUrl('api/list'), function(err, result) {
// Test the length of array result
var len = result.data && result.data.length;
test.isTrue(!!len, 'Result was empty');
// Get the object
var obj = result.data && result.data[0] || {};
test.equal(obj.text, 'OK', 'Didnt get the expected result');
// Set the id for the next test
id = obj._id;
onComplete();
});
});
Tinytest.addAsync('http-publish - client - get list from custom prefix', function (test, onComplete) {
// Now test the one we added with a custom prefix
HTTP.get(Meteor.absoluteUrl('api2/list'), function(err, result) {
// Test the length of array result
var len = result.data && result.data.length;
test.isTrue(!!len, 'Result was empty');
// Get the object
var obj = result.data && result.data[0] || {};
test.equal(obj.text, 'OK', 'Didnt get the expected result');
onComplete();
});
});
Tinytest.addAsync('http-publish - client - unmountCustom', function (test, onComplete) {
// Now unmount the methods with custom prefix
test.isTrue(true);
Meteor.call('unmountCustom', function(err, result) {
test.isTrue(result);
onComplete();
});
test.isTrue(true);
});
Tinytest.addAsync('http-publish - client - custom unmounted', function (test, onComplete) {
// Now test the one we added with a custom prefix
HTTP.get(Meteor.absoluteUrl('api2/list'), function(err, result) {
test.isTrue(!!err, "Should have received an error since we unmounted the custom rest points");
onComplete();
});
});
Tinytest.addAsync('http-publish - client - put list', function (test, onComplete) {
test.isTrue(id !== '', 'No id is set?');
// Update the data
HTTP.put(Meteor.absoluteUrl('api/list/' + id), {
data: {
$set: { text: 'UPDATED' }
}
}, function(err, result) {
var resultId = result.data && result.data._id;
test.isTrue(resultId !== undefined, 'Didnt get the expected id in result');
// Check if data is updated
HTTP.get(Meteor.absoluteUrl('api/list'), function(err, result) {
var len = result.data && result.data.length;
test.isTrue(!!len, 'Result was empty');
var obj = result.data && result.data[0] || {};
test.equal(obj.text, 'UPDATED', 'Didnt get the expected result');
onComplete();
});
});
});
Tinytest.addAsync('http-publish - client - insert/remove list', function (test, onComplete) {
// Insert a doc
HTTP.post(Meteor.absoluteUrl('api/list'), {
data: {
text: 'INSERTED'
}
}, function(err, result) {
var resultId = result.data && result.data._id;
test.isTrue(resultId !== undefined, 'Didnt get the expected id in result');
// Delete the doc
HTTP.del(Meteor.absoluteUrl('api/list/' + resultId), function(err, result) {
removedId = result.data && result.data._id;
test.isTrue(removedId !== undefined, 'Didnt get the expected id in result');
onComplete();
});
});
});
Tinytest.addAsync('http-publish - client - check removed', function (test, onComplete) {
test.isTrue(removedId !== '', 'No removedId is set?');
HTTP.get(Meteor.absoluteUrl('api/list/' + removedId), function(err, result) {
var obj = result.data || {};
test.isTrue(obj._id === undefined, 'Item was not removed');
test.isTrue(err.response.statusCode === 404, 'Item was not removed');
onComplete();
});
});
Tinytest.addAsync('http-publish - client - check findOne', function (test, onComplete) {
test.isTrue(id !== '', 'No id is set?');
HTTP.get(Meteor.absoluteUrl('api/list/' + id), function(err, result) {
var obj = result.data || {};
test.isTrue(obj._id !== undefined, 'expected a document');
test.isTrue(obj.text === 'UPDATED', 'expected text === UPDATED');
onComplete();
});
});
// Check if removedId found
// Check if id still found
//Test API:
//test.isFalse(v, msg)
//test.isTrue(v, msg)
//test.equalactual, expected, message, not
//test.length(obj, len)
//test.include(s, v)
//test.isNaN(v, msg)
//test.isUndefined(v, msg)
//test.isNotNull
//test.isNull
//test.throws(func)
//test.instanceOf(obj, klass)
//test.notEqual(actual, expected, message)
//test.runId()
//test.exception(exception)
//test.expect_fail()
//test.ok(doc)
//test.fail(doc)
//test.equal(a, b, msg)

View file

@ -0,0 +1,149 @@
function equals(a, b) {
return !!(EJSON.stringify(a) === EJSON.stringify(b));
}
Tinytest.add('http-publish - server - test environment', function(test) {
test.isTrue(typeof _publishHTTP !== 'undefined', 'test environment not initialized _publishHTTP');
test.isTrue(typeof HTTP !== 'undefined', 'test environment not initialized HTTP');
test.isTrue(typeof HTTP.publish !== 'undefined', 'test environment not initialized HTTP.publish');
test.isTrue(typeof HTTP.unpublish !== 'undefined', 'test environment not initialized HTTP.unpublish');
test.isTrue(typeof HTTP.publishFormats !== 'undefined', 'test environment not initialized HTTP.publishFormats');
});
list = new Meteor.Collection('list');
console.log('Server url: ' + Meteor.absoluteUrl());
list.allow({
insert: function() { return true; },
update: function() { return true; },
remove: function() { return true; }
});
console.log('Rig publish');
HTTP.publish({collection: list}, function() {
return list.find();
});
// Test custom prefix, too
HTTP.publish({collection: list, name: '/api2/list'}, function() {
return list.find();
});
Meteor.methods({
clearTest: function() {
console.log('Client called clearTest');
// Empty test db
list.remove({});
// Insert one text
list.insert({ text: 'OK' });
// Count
var count = list.find().count();
return !!(count === 1);
},
unmountCustom: function() {
console.log('Client called unmountCustom');
_publishHTTP.unpublish('/api2/list');
return true;
}
});
Tinytest.add('http-publish - server - getMethodHandler', function(test) {
try {
var methodHandler = _publishHTTP.getMethodHandler(list, 'insert');
test.isTrue(typeof methodHandler === 'function', 'expected getMethodHandler to return a function');
} catch(err) {
test.fail(err.message);
}
});
Tinytest.add('http-publish - server - formatHandlers', function(test) {
test.isTrue(typeof _publishHTTP.formatHandlers.json === 'function', 'Cant find formatHandler for json');
var testScope = {
code: 0,
setContentType: function(code) {
this.code = code;
}
};
var resultFormatHandler = _publishHTTP.formatHandlers.json.apply(testScope, [{test:'ok'}]);
test.equal(testScope.code, 'application/json', 'json formatHandler have not set setContentType');
test.equal(resultFormatHandler, '{"test":"ok"}', 'json formatHandler returned a bad result');
});
Tinytest.add('http-publish - server - getPublishScope', function(test) {
var oldScope = {
userId: '1',
params: '2',
query: '3',
oldStuff: 'hmmm'
};
var newScope = _publishHTTP.getPublishScope(oldScope);
test.isUndefined(newScope.oldStuff, 'This oldStuff should not be in the new scope');
test.equal(newScope.userId, '1', 'userId not set in the new scope');
test.equal(newScope.params, '2', 'params not set in the new scope');
test.equal(newScope.query, '3', 'query not set in the new scope');
});
Tinytest.add('http-publish - server - formatResult', function(test) {
var oldScope = {
statusCode: 200,
userId: '1',
params: '2',
query: '3',
oldStuff: 'hmmm',
setStatusCode: function(code) {
this.statusCode = code;
},
code: 0,
setContentType: function(code) {
this.code = code;
}
};
var result = _publishHTTP.formatResult({test: 'ok'}, oldScope);
test.equal(oldScope.code, 'application/json', 'json formatHandler have not set setContentType');
test.equal(result, '{"test":"ok"}', 'json formatHandler returned a bad result');
});
//Test API:
//test.isFalse(v, msg)
//test.isTrue(v, msg)
//test.equalactual, expected, message, not
//test.length(obj, len)
//test.include(s, v)
//test.isNaN(v, msg)
//test.isUndefined(v, msg)
//test.isNotNull
//test.isNull
//test.throws(func)
//test.instanceOf(obj, klass)
//test.notEqual(actual, expected, message)
//test.runId()
//test.exception(exception)
//test.expect_fail()
//test.ok(doc)
//test.fail(doc)
//test.equal(a, b, msg)

View file

@ -0,0 +1,330 @@
## Public and Private API ##
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
***
__File: ["http.publish.server.api.js"](http.publish.server.api.js) Where: {server}__
***
### <a name="_publishHTTP"></a>_publishHTTP {any}&nbsp;&nbsp;<sub><i>Server</i></sub> ###
```
GET /note
GET /note/:id
POST /note
PUT /note/:id
DELETE /note/:id
```
> ```_publishHTTP = { ...``` [http.publish.server.api.js:20](http.publish.server.api.js#L20)
-
Could be cool if we could serve some api doc or even an api script
user could do <script href="/note/api?token=1&user=2"></script> and be served
a client-side javascript api?
Eg.
HTTP.api.note.create();
HTTP.api.login(username, password);
HTTP.api.logout
-
### <a name="_publishHTTP.getPublishScope"></a>*_publishhttp*.getPublishScope(scope)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method is private*
*This method __getPublishScope__ is defined in `_publishHTTP`*
__Arguments__
* __scope__ *{Object}*
__Returns__ *{httpPublishGetPublishScope.publishScope}*
Creates a nice scope for the publish method
> ```_publishHTTP.getPublishScope = function httpPublishGetPublishScope(scope) { ...``` [http.publish.server.api.js:35](http.publish.server.api.js#L35)
-
### <a name="_publishHTTP.formatHandlers.json"></a>*_publishhttpformathandlers*.json(result)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method is private*
*This method __json__ is defined in `_publishHTTP.formatHandlers`*
__Arguments__
* __result__ *{Object}*
The result object
__Returns__ *{String}*
JSON
Formats the output into JSON and sets the appropriate content type on `this`
> ```_publishHTTP.formatHandlers.json = function httpPublishJSONFormatHandler(result) { ...``` [http.publish.server.api.js:56](http.publish.server.api.js#L56)
-
### <a name="_publishHTTP.formatResult"></a>*_publishhttp*.formatResult(result, scope, [defaultFormat])&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method is private*
*This method __formatResult__ is defined in `_publishHTTP`*
__Arguments__
* __result__ *{Object}*
The result object
* __scope__ *{Object}*
* __defaultFormat__ *{String}* (Optional, Default = 'json')
Default format to use if format is not in query string.
__Returns__ *{Any}*
The formatted result
Formats the result into the format selected by querystring eg. "&format=json"
> ```_publishHTTP.formatResult = function httpPublishFormatResult(result, scope, defaultFormat) { ...``` [http.publish.server.api.js:73](http.publish.server.api.js#L73)
-
### <a name="_publishHTTP.error"></a>*_publishhttp*.error(statusCode, message, scope)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method is private*
*This method __error__ is defined in `_publishHTTP`*
__Arguments__
* __statusCode__ *{String}*
The status code
* __message__ *{String}*
The message
* __scope__ *{Object}*
__Returns__ *{Any}*
The formatted result
Responds with error message in the expected format
> ```_publishHTTP.error = function httpPublishError(statusCode, message, scope) { ...``` [http.publish.server.api.js:114](http.publish.server.api.js#L114)
-
### <a name="_publishHTTP.getMethodHandler"></a>*_publishhttp*.getMethodHandler(collection, methodName)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method is private*
*This method __getMethodHandler__ is defined in `_publishHTTP`*
__Arguments__
* __collection__ *{[Meteor.Collection](#Meteor.Collection)}*
The Meteor.Collection instance
* __methodName__ *{String}*
The method name
__Returns__ *{Function}*
The server method
Returns the DDP connection handler, already setup and secured
> ```_publishHTTP.getMethodHandler = function httpPublishGetMethodHandler(collection, methodName) { ...``` [http.publish.server.api.js:129](http.publish.server.api.js#L129)
-
### <a name="_publishHTTP.unpublishList"></a>*_publishhttp*.unpublishList(names)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method is private*
*This method __unpublishList__ is defined in `_publishHTTP`*
__Arguments__
* __names__ *{Array}*
List of method names to unpublish
__Returns__ *{undefined}*
Unpublishes all HTTP methods that have names matching the given list.
> ```_publishHTTP.unpublishList = function httpPublishUnpublishList(names) { ...``` [http.publish.server.api.js:149](http.publish.server.api.js#L149)
-
### <a name="_publishHTTP.unpublish"></a>*_publishhttp*.unpublish([name])&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method is private*
*This method __unpublish__ is defined in `_publishHTTP`*
__Arguments__
* __name__ *{String|[Meteor.Collection](#Meteor.Collection)}* (Optional)
The method name or collection
__Returns__ *{undefined}*
Unpublishes all HTTP methods that were published with the given name or
for the given collection. Call with no arguments to unpublish all.
> ```_publishHTTP.unpublish = function httpPublishUnpublish(``` [http.publish.server.api.js:177](http.publish.server.api.js#L177)
-
### <a name="HTTP.publishFormats"></a>*http*.publishFormats(newHandlers)&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __publishFormats__ is defined in `HTTP`*
__Arguments__
* __newHandlers__ *{Object}*
__Returns__ *{undefined}*
Add publish formats. Example:
```js
HTTP.publishFormats({
json: function(inputObject) {
// Set the method scope content type to json
this.setContentType('application/json');
// Return EJSON string
return EJSON.stringify(inputObject);
}
});
```
> ```HTTP.publishFormats = function httpPublishFormats(newHandlers) { ...``` [http.publish.server.api.js:215](http.publish.server.api.js#L215)
-
### <a name="HTTP.publish"></a>*http*.publish(options, [name], [collection], [publishFunc])&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __publish__ is defined in `HTTP`*
__Arguments__
* __options__ *{Object}*
* __defaultFormat__ *{String}* (Optional, Default = 'json')
Format to use for responses when `format` is not found in the query string.
* __collectionGet__ *{String}* (Optional, Default = true)
Add GET restpoint for collection? Requires a publish function.
* __collectionPost__ *{String}* (Optional, Default = true)
Add POST restpoint for adding documents to the collection?
* __documentGet__ *{String}* (Optional, Default = true)
Add GET restpoint for documents in collection? Requires a publish function.
* __documentPut__ *{String}* (Optional, Default = true)
Add PUT restpoint for updating a document in the collection?
* __documentDelete__ *{String}* (Optional, Default = true)
Add DELETE restpoint for deleting a document in the collection?
* __name__ *{String}* (Optional)
Restpoint name (url prefix). Optional if `collection` is passed. Will mount on `/api/collectionName` by default.
* __collection__ *{[Meteor.Collection](#Meteor.Collection)}* (Optional)
Meteor.Collection instance. Required for all restpoints except collectionGet
* __publishFunc__ *{Function}* (Optional)
A publish function. Required to mount GET restpoints.
__Returns__ *{undefined}*
__TODO__
```
* this should use options argument instead of optional args
```
Publishes one or more restpoints, mounted on "name" ("/api/collectionName/"
by default). The GET restpoints are subscribed to the document set (cursor)
returned by the publish function you supply. The other restpoints forward
requests to Meteor's built-in DDP methods (insert, update, remove), meaning
that full allow/deny security is automatic.
__Usage:__
Publish only:
HTTP.publish({name: 'mypublish'}, publishFunc);
Publish and mount crud rest point for collection /api/myCollection:
HTTP.publish({collection: myCollection}, publishFunc);
Mount CUD rest point for collection and documents without GET:
HTTP.publish({collection: myCollection});
> ```HTTP.publish = function httpPublish(options, publishFunc) { ...``` [http.publish.server.api.js:256](http.publish.server.api.js#L256)
-
### <a name="HTTP.unpublish"></a>*http*.unpublish([name])&nbsp;&nbsp;<sub><i>Server</i></sub> ###
*This method __unpublish__ is defined in `HTTP`*
__Arguments__
* __name__ *{String|[Meteor.Collection](#Meteor.Collection)}* (Optional)
The method name or collection
__Returns__ *{undefined}*
Unpublishes all HTTP methods that were published with the given name or
for the given collection. Call with no arguments to unpublish all.
> ```HTTP.unpublish = _publishHTTP.unpublish;``` [http.publish.server.api.js:453](http.publish.server.api.js#L453)

View file

@ -0,0 +1,34 @@
Package.describe({
git: 'https://github.com/zcfs/Meteor-http-publish.git',
name: 'wekan-cfs-http-publish',
version: '0.0.13',
summary: 'Adds HTTP.publish and HTTP.unpublish RESTful'
});
Package.onUse(function(api) {
api.versionsFrom('1.0');
api.use(['webapp', 'underscore', 'ejson', 'random'], 'server');
api.use('wekan-cfs-http-methods@0.0.27');
api.imply && api.imply('wekan-cfs-http-methods');
api.export && api.export('_publishHTTP', { testOnly: true });
api.addFiles('http.publish.client.api.js', 'client');
api.addFiles('http.publish.server.api.js', 'server');
});
Package.onTest(function (api) {
api.use('wekan-cfs-http-publish', ['client', 'server']);
api.use('test-helpers', ['client', 'server']);
api.use('http', 'client');
api.use(['tinytest', 'underscore', 'ejson', 'ordered-dict',
'random', 'deps']);
api.addFiles('http.publish.tests.server.js', 'server');
api.addFiles('http.publish.tests.client.js', 'client');
});

View file

@ -0,0 +1,2 @@
/http-publish
/http-methods