mirror of
https://github.com/wekan/wekan.git
synced 2025-12-19 17:00:13 +01:00
Fixed Non-ASCII attachment filename will crash when downloading.
Thanks to xet7 ! Fixes #2759
This commit is contained in:
parent
843ff8eaaa
commit
c2da477735
277 changed files with 30568 additions and 52 deletions
5
packages/wekan-cfs-access-point/.travis.yml
Normal file
5
packages/wekan-cfs-access-point/.travis.yml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
language: node_js
|
||||
node_js:
|
||||
- "0.10"
|
||||
before_install:
|
||||
- "curl -L http://git.io/s0Zu-w | /bin/sh"
|
||||
288
packages/wekan-cfs-access-point/CHANGELOG.md
Normal file
288
packages/wekan-cfs-access-point/CHANGELOG.md
Normal file
|
|
@ -0,0 +1,288 @@
|
|||
# Changelog
|
||||
|
||||
## [v0.1.50] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.46)
|
||||
#### 21/1/19 by Harry Adel
|
||||
|
||||
- Bump to version 0.1.50
|
||||
|
||||
- *Merged pull-request:* "filename conversion for FS.HTTP.Handlers.Get" [#9](https://github.com/zcfs/Meteor-CollectionFS/pull/994) ([yatusiter](https://github.com/yatusiter))
|
||||
|
||||
|
||||
|
||||
## [v0.1.46] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.46)
|
||||
#### 30/3/15 by Eric Dobbertin
|
||||
|
||||
- Bump to version 0.1.46
|
||||
|
||||
- *Merged pull-request:* [#611](https://github.com/zcfs/Meteor-CollectionFS/issues/611)
|
||||
|
||||
- Exposed request handlers on `FS.HTTP.Handlers` object so that app can override
|
||||
|
||||
## [v0.1.43] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.43)
|
||||
#### 20/12/14 by Morten Henriksen
|
||||
- add changelog
|
||||
|
||||
- Bump to version 0.1.43
|
||||
|
||||
- *Fixed bug:* "Doesn't work in IE 8" [#10](https://github.com/zcfs/Meteor-cfs-access-point/issues/10)
|
||||
|
||||
- *Merged pull-request:* "rootUrlPathPrefix fix for cordova" [#9](https://github.com/zcfs/Meteor-cfs-access-point/issues/9) ([dmitriyles](https://github.com/dmitriyles))
|
||||
|
||||
- *Merged pull-request:* "Support for expiration token" [#1](https://github.com/zcfs/Meteor-cfs-access-point/issues/1) ([tanis2000](https://github.com/tanis2000))
|
||||
|
||||
Patches by GitHub users [@dmitriyles](https://github.com/dmitriyles), [@tanis2000](https://github.com/tanis2000).
|
||||
|
||||
## [v0.1.42] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.42)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
## [v0.1.41] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.41)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- mbr update, remove versions.json
|
||||
|
||||
- Cordova rootUrlPathPrefix fix
|
||||
|
||||
- Bump to version 0.1.41
|
||||
|
||||
## [v0.1.40] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.1.40)
|
||||
#### 17/12/14 by Morten Henriksen
|
||||
- mbr fixed warnings
|
||||
|
||||
- fixes to GET handler
|
||||
|
||||
- add back tests
|
||||
|
||||
- support apps in server subdirectories; closes #8
|
||||
|
||||
- 0.9.1 support
|
||||
|
||||
## [v0.0.39] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.39)
|
||||
#### 28/08/14 by Morten Henriksen
|
||||
- Meteor Package System Update
|
||||
|
||||
## [v0.0.38] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.38)
|
||||
#### 27/08/14 by Eric Dobbertin
|
||||
## [v0.0.37] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.37)
|
||||
#### 26/08/14 by Eric Dobbertin
|
||||
- change package name to lowercase
|
||||
|
||||
## [v0.0.36] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.36)
|
||||
#### 06/08/14 by Eric Dobbertin
|
||||
- pass correct arg
|
||||
|
||||
## [v0.0.35] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.35)
|
||||
#### 06/08/14 by Eric Dobbertin
|
||||
- move to correct place
|
||||
|
||||
## [v0.0.34] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.34)
|
||||
#### 05/08/14 by Eric Dobbertin
|
||||
- *Merged pull-request:* "Added contentLength for ranges and inline content" [#5](https://github.com/zcfs/Meteor-cfs-access-point/issues/5) ([maomorales](https://github.com/maomorales))
|
||||
|
||||
- Content-Length and Last-Modified headers
|
||||
|
||||
Patches by GitHub user [@maomorales](https://github.com/maomorales).
|
||||
|
||||
## [v0.0.33] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.33)
|
||||
#### 31/07/14 by Eric Dobbertin
|
||||
- *Merged pull-request:* "Force browser to download with filename passed in url" [#3](https://github.com/zcfs/Meteor-cfs-access-point/issues/3) ([elbowz](https://github.com/elbowz))
|
||||
|
||||
- Force browser to download with filename passed in url
|
||||
|
||||
Patches by GitHub user [@elbowz](https://github.com/elbowz).
|
||||
|
||||
## [v0.0.32] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.32)
|
||||
#### 28/07/14 by Eric Dobbertin
|
||||
- support collection-specific GET headers
|
||||
|
||||
- update API docs
|
||||
|
||||
## [v0.0.31] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.31)
|
||||
#### 06/07/14 by Eric Dobbertin
|
||||
- allow override filename
|
||||
|
||||
## [v0.0.30] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.30)
|
||||
#### 30/04/14 by Eric Dobbertin
|
||||
- ignore auth on server so that url method can be called on the server
|
||||
|
||||
## [v0.0.29] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.29)
|
||||
#### 30/04/14 by Eric Dobbertin
|
||||
- rework the new authtoken stuff to make it easier to debug and cleaner
|
||||
|
||||
## [v0.0.28] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.28)
|
||||
#### 29/04/14 by Eric Dobbertin
|
||||
- generate api docs
|
||||
|
||||
- adjustments to use new FS.File API functions, plus have `url` function omit query string whenever possible
|
||||
|
||||
- *Merged pull-request:* "Support for expiration token" [#1](https://github.com/zcfs/Meteor-cfs-access-point/issues/1) ([tanis2000](https://github.com/tanis2000))
|
||||
|
||||
- Switched to HTTP.call() to get the server time
|
||||
|
||||
- Better check for options.auth being a number. Check to see if we have Buffer() available on the server side. New check to make sure we have the token. Switched Metheor.method to HTTP.methods for the getServerTime() function.
|
||||
|
||||
- Expiration is now optional. If auth is set to a number, that is the number of seconds the token is valid for.
|
||||
|
||||
- Added time sync with the server for token generation.
|
||||
|
||||
- Added code to pass a token with a set expiration date from the client. Added token check on the server side.
|
||||
|
||||
Patches by GitHub user [@tanis2000](https://github.com/tanis2000).
|
||||
|
||||
## [v0.0.27] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.27)
|
||||
#### 08/04/14 by Eric Dobbertin
|
||||
- clean up/fix whole-file upload handler
|
||||
|
||||
## [v0.0.26] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.26)
|
||||
#### 07/04/14 by Eric Dobbertin
|
||||
- add URL options to get temporary images while uploading and storing
|
||||
|
||||
## [v0.0.25] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.25)
|
||||
#### 03/04/14 by Eric Dobbertin
|
||||
- * allow `setBaseUrl` to be called either outside of Meteor.startup or inside * move encodeParams helper to FS.Utility
|
||||
|
||||
## [v0.0.24] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.24)
|
||||
#### 03/04/14 by Eric Dobbertin
|
||||
- properly remount URLs
|
||||
|
||||
- when uploading chunks, check the insert allow/deny since it's part of inserting
|
||||
|
||||
## [v0.0.23] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.23)
|
||||
#### 31/03/14 by Eric Dobbertin
|
||||
- use latest releases
|
||||
|
||||
## [v0.0.22] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.22)
|
||||
#### 29/03/14 by Morten Henriksen
|
||||
- remove underscore deps
|
||||
|
||||
## [v0.0.21] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.21)
|
||||
#### 25/03/14 by Morten Henriksen
|
||||
- add comments about shareId
|
||||
|
||||
## [v0.0.20] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.20)
|
||||
#### 23/03/14 by Morten Henriksen
|
||||
- Rollback to specific git dependency
|
||||
|
||||
- Try modified test script
|
||||
|
||||
- deps are already in collectionFS
|
||||
|
||||
## [v0.0.19] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.19)
|
||||
#### 22/03/14 by Morten Henriksen
|
||||
- try to fix travis test by using general package references
|
||||
|
||||
## [v0.0.18] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.18)
|
||||
#### 22/03/14 by Morten Henriksen
|
||||
- If the read stream fails we send an error to the client
|
||||
|
||||
## [v0.0.17] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.17)
|
||||
#### 21/03/14 by Morten Henriksen
|
||||
- remove smart lock
|
||||
|
||||
- commit smart.lock, trying to get tests to pass on travis
|
||||
|
||||
- some minor pkg adjustments; trying to get tests to pass on travis
|
||||
|
||||
## [v0.0.16] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.16)
|
||||
#### 18/03/14 by Morten Henriksen
|
||||
- Rollback to using the direct storage adapter - makes more sense when serving files
|
||||
|
||||
- shift to new http.methods streaming api
|
||||
|
||||
- move server side DDP access points to cfs-download-ddp pkg; update API docs
|
||||
|
||||
- fix typo...
|
||||
|
||||
- return something useful
|
||||
|
||||
- convert to streaming
|
||||
|
||||
- Add streaming WIP
|
||||
|
||||
- fix/adjust some tests; minor improvements to some handlers
|
||||
|
||||
- Add unmount and allow mount to use default selector function
|
||||
|
||||
- Refactor access point - wip
|
||||
|
||||
## [v0.0.15] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.15)
|
||||
#### 05/03/14 by Morten Henriksen
|
||||
- Refactor note, encode stuff should be prefixed into FS.Utility
|
||||
|
||||
- FS.File.url add user deps when auth is used
|
||||
|
||||
- fix url method
|
||||
|
||||
- query string fix
|
||||
|
||||
- move PUT access points for HTTP upload into this package; mount DELETE on /record/ as well as /files/; some fixes and improvements to handlers
|
||||
|
||||
## [v0.0.14] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.14)
|
||||
#### 03/03/14 by Eric Dobbertin
|
||||
- better error; return Buffer instead of converting to Uint8Array
|
||||
|
||||
## [v0.0.13] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.13)
|
||||
#### 02/03/14 by Eric Dobbertin
|
||||
- more tests, make everything work, add unpublish method
|
||||
|
||||
- Merge branch 'master' of https://github.com/zcfs/Meteor-cfs-access-point
|
||||
|
||||
## [v0.0.12] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.12)
|
||||
#### 01/03/14 by Eric Dobbertin
|
||||
- add travis-ci image
|
||||
|
||||
- rework URLs a bit, use http-publish package to publish FS.Collection listing, and add a test for this (!)
|
||||
|
||||
- add http-publish dependency
|
||||
|
||||
- del should be delete
|
||||
|
||||
## [v0.0.11] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.11)
|
||||
#### 28/02/14 by Eric Dobbertin
|
||||
- move some code to other packages; redo the HTTP GET/DEL methods
|
||||
|
||||
## [v0.0.10] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.10)
|
||||
#### 28/02/14 by Eric Dobbertin
|
||||
- move DDP upload methods to new cfs-upload-ddp package
|
||||
|
||||
## [v0.0.9] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.9)
|
||||
#### 21/02/14 by Eric Dobbertin
|
||||
- new URL syntax; use the store's file key instead of ID; also fix allow/deny checks with insecure
|
||||
|
||||
## [v0.0.8] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.8)
|
||||
#### 20/02/14 by Eric Dobbertin
|
||||
- support HTTP PUT of new file and fix PUT of existing file
|
||||
|
||||
## [v0.0.7] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.7)
|
||||
#### 17/02/14 by Morten Henriksen
|
||||
- add http-methods dependency
|
||||
|
||||
## [v0.0.6] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.6)
|
||||
#### 16/02/14 by Morten Henriksen
|
||||
## [v0.0.5] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.5)
|
||||
#### 16/02/14 by Morten Henriksen
|
||||
- a few fixes and improvements
|
||||
|
||||
- need to actually mount it
|
||||
|
||||
- attempt at switching to generic HTTP access point; also add support for chunked http downloads (range header)
|
||||
|
||||
## [v0.0.4] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.4)
|
||||
#### 15/02/14 by Morten Henriksen
|
||||
- Merge branch 'master' of https://github.com/zcfs/Meteor-cfs-access-point
|
||||
|
||||
- corrected typo
|
||||
|
||||
- added debugging
|
||||
|
||||
- call HTTP.methods on server only
|
||||
|
||||
- run client side, too, for side effects
|
||||
|
||||
- rework for additional abstraction; also DDP methods don't need to be per-collection so they no longer are
|
||||
|
||||
## [v0.0.3] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.3)
|
||||
#### 13/02/14 by Morten Henriksen
|
||||
## [v0.0.2] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.2)
|
||||
#### 13/02/14 by Morten Henriksen
|
||||
## [v0.0.1] (https://github.com/zcfs/Meteor-cfs-access-point/tree/v0.0.1)
|
||||
#### 13/02/14 by Morten Henriksen
|
||||
- init commit
|
||||
|
||||
20
packages/wekan-cfs-access-point/LICENSE.md
Normal file
20
packages/wekan-cfs-access-point/LICENSE.md
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 [@raix](https://github.com/raix) and [@aldeed](https://github.com/aldeed), 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.
|
||||
32
packages/wekan-cfs-access-point/README.md
Normal file
32
packages/wekan-cfs-access-point/README.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
wekan-cfs-access-point [](https://travis-ci.org/CollectionFS/Meteor-cfs-access-point)
|
||||
=========================
|
||||
|
||||
This is a Meteor package used by
|
||||
[CollectionFS](https://github.com/zcfs/Meteor-CollectionFS).
|
||||
|
||||
You don't need to manually add this package to your app. It is added when you
|
||||
add the `wekan-cfs-standard-packages` package. You could potentially use your own access point
|
||||
package instead.
|
||||
|
||||
## Define a URL for Collection Listing
|
||||
|
||||
To define a URL that accepts GET requests and returns a list of published
|
||||
files in a FS.Collection:
|
||||
|
||||
```js
|
||||
Images = new FS.Collection("images", {
|
||||
stores: [myStore]
|
||||
});
|
||||
|
||||
FS.HTTP.publish(Images, function () {
|
||||
// `this` provides a context similar to Meteor.publish
|
||||
return Images.find();
|
||||
});
|
||||
```
|
||||
|
||||
The URL will be '/cfs/record/images', where the `cfs` piece is configurable
|
||||
using the `FS.HTTP.setBaseUrl` method.
|
||||
|
||||
## API Documentation
|
||||
|
||||
[Here](api.md)
|
||||
58
packages/wekan-cfs-access-point/access-point-client.js
Normal file
58
packages/wekan-cfs-access-point/access-point-client.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
FS.HTTP.setHeadersForGet = function setHeadersForGet() {
|
||||
// Client Stub
|
||||
};
|
||||
|
||||
FS.HTTP.now = function() {
|
||||
return new Date(new Date() + FS.HTTP._serverTimeDiff);
|
||||
};
|
||||
|
||||
// Returns the localstorage if its found and working
|
||||
// TODO: check if this works in IE
|
||||
// could use Meteor._localStorage - just needs a rewrite
|
||||
FS.HTTP._storage = function() {
|
||||
var storage,
|
||||
fail,
|
||||
uid;
|
||||
try {
|
||||
uid = "test";
|
||||
(storage = window.localStorage).setItem(uid, uid);
|
||||
fail = (storage.getItem(uid) !== uid);
|
||||
storage.removeItem(uid);
|
||||
if (fail) {
|
||||
storage = false;
|
||||
}
|
||||
} catch(e) {
|
||||
console.log("Error initializing storage for FS.HTTP");
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
return storage;
|
||||
};
|
||||
|
||||
// get our storage if found
|
||||
FS.HTTP.storage = FS.HTTP._storage();
|
||||
|
||||
FS.HTTP._prefix = 'fsHTTP.';
|
||||
|
||||
FS.HTTP._serverTimeDiff = 0; // Time difference in ms
|
||||
|
||||
if (FS.HTTP.storage) {
|
||||
// Initialize the FS.HTTP._serverTimeDiff
|
||||
FS.HTTP._serverTimeDiff = (1*FS.HTTP.storage.getItem(FS.HTTP._prefix+'timeDiff')) || 0;
|
||||
// At client startup we figure out the time difference between server and
|
||||
// client time - this includes lag and timezone
|
||||
Meteor.startup(function() {
|
||||
// Call the server method an get server time
|
||||
HTTP.get(rootUrlPathPrefix + '/cfs/servertime', function(error, result) {
|
||||
if (!error) {
|
||||
// Update our server time diff
|
||||
var dateNew = new Date(+result.content);
|
||||
FS.HTTP._serverTimeDiff = dateNew - new Date();// - lag or/and timezone
|
||||
// Update the localstorage
|
||||
FS.HTTP.storage.setItem(FS.HTTP._prefix + 'timeDiff', FS.HTTP._serverTimeDiff);
|
||||
} else {
|
||||
console.log(error.message);
|
||||
}
|
||||
}); // EO Server call
|
||||
});
|
||||
}
|
||||
199
packages/wekan-cfs-access-point/access-point-common.js
Normal file
199
packages/wekan-cfs-access-point/access-point-common.js
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
rootUrlPathPrefix = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || "";
|
||||
// Adjust the rootUrlPathPrefix if necessary
|
||||
if (rootUrlPathPrefix.length > 0) {
|
||||
if (rootUrlPathPrefix.slice(0, 1) !== '/') {
|
||||
rootUrlPathPrefix = '/' + rootUrlPathPrefix;
|
||||
}
|
||||
if (rootUrlPathPrefix.slice(-1) === '/') {
|
||||
rootUrlPathPrefix = rootUrlPathPrefix.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
// prepend ROOT_URL when isCordova
|
||||
if (Meteor.isCordova) {
|
||||
rootUrlPathPrefix = Meteor.absoluteUrl(rootUrlPathPrefix.replace(/^\/+/, '')).replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
baseUrl = '/cfs';
|
||||
FS.HTTP = FS.HTTP || {};
|
||||
|
||||
// Note the upload URL so that client uploader packages know what it is
|
||||
FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files';
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.setBaseUrl
|
||||
* @public
|
||||
* @param {String} newBaseUrl - Change the base URL for the HTTP GET and DELETE endpoints.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) {
|
||||
|
||||
// Adjust the baseUrl if necessary
|
||||
if (newBaseUrl.slice(0, 1) !== '/') {
|
||||
newBaseUrl = '/' + newBaseUrl;
|
||||
}
|
||||
if (newBaseUrl.slice(-1) === '/') {
|
||||
newBaseUrl = newBaseUrl.slice(0, -1);
|
||||
}
|
||||
|
||||
// Update the base URL
|
||||
baseUrl = newBaseUrl;
|
||||
|
||||
// Change the upload URL so that client uploader packages know what it is
|
||||
FS.HTTP.uploadUrl = rootUrlPathPrefix + baseUrl + '/files';
|
||||
|
||||
// Remount URLs with the new baseUrl, unmounting the old, on the server only.
|
||||
// If existingMountPoints is empty, then we haven't run the server startup
|
||||
// code yet, so this new URL will be used at that point for the initial mount.
|
||||
if (Meteor.isServer && !FS.Utility.isEmpty(_existingMountPoints)) {
|
||||
mountUrls();
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* FS.File extensions
|
||||
*/
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.urlRelative Construct the file url
|
||||
* @public
|
||||
* @param {Object} [options]
|
||||
* @param {String} [options.store] Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
|
||||
* @param {Boolean} [options.auth=null] Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
|
||||
* @param {Boolean} [options.download=false] Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
|
||||
* @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
|
||||
* @param {Boolean} [options.returnWhenStored=false] Flag relevant only on server, Return the URL only when file has been saved to the requested store.
|
||||
* @param {Boolean} [options.metadata=false] Return the URL for the file metadata access point rather than the file itself.
|
||||
* @param {String} [options.uploading=null] A URL to return while the file is being uploaded.
|
||||
* @param {String} [options.storing=null] A URL to return while the file is being stored.
|
||||
* @param {String} [options.filename=null] Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
|
||||
*
|
||||
* Returns the relative HTTP URL for getting the file or its metadata.
|
||||
*/
|
||||
FS.File.prototype.urlRelative = function(options) {
|
||||
var self = this;
|
||||
options = options || {};
|
||||
options = FS.Utility.extend({
|
||||
store: null,
|
||||
auth: null,
|
||||
download: false,
|
||||
metadata: false,
|
||||
brokenIsFine: false,
|
||||
returnWhenStored: false,
|
||||
uploading: null, // return this URL while uploading
|
||||
storing: null, // return this URL while storing
|
||||
filename: null // override the filename that is shown to the user
|
||||
}, options.hash || options); // check for "hash" prop if called as helper
|
||||
|
||||
// Primarily useful for displaying a temporary image while uploading an image
|
||||
if (options.uploading && !self.isUploaded()) {
|
||||
return options.uploading;
|
||||
}
|
||||
|
||||
if (self.isMounted()) {
|
||||
// See if we've stored in the requested store yet
|
||||
var storeName = options.store || self.collection.primaryStore.name;
|
||||
if (!self.hasStored(storeName)) {
|
||||
if (options.storing) {
|
||||
return options.storing;
|
||||
} else if (!options.brokenIsFine) {
|
||||
// In case we want to get back the url only when he is stored
|
||||
if (Meteor.isServer && options.returnWhenStored) {
|
||||
// Wait till file is stored to storeName
|
||||
self.onStored(storeName);
|
||||
} else {
|
||||
// We want to return null if we know the URL will be a broken
|
||||
// link because then we can avoid rendering broken links, broken
|
||||
// images, etc.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add filename to end of URL if we can determine one
|
||||
var filename = options.filename || self.name({store: storeName});
|
||||
if (typeof filename === "string" && filename.length) {
|
||||
filename = '/' + filename;
|
||||
} else {
|
||||
filename = '';
|
||||
}
|
||||
|
||||
// TODO: Could we somehow figure out if the collection requires login?
|
||||
var authToken = '';
|
||||
if (Meteor.isClient && typeof Accounts !== "undefined" && typeof Accounts._storedLoginToken === "function") {
|
||||
if (options.auth !== false) {
|
||||
// Add reactive deps on the user
|
||||
Meteor.userId();
|
||||
|
||||
var authObject = {
|
||||
authToken: Accounts._storedLoginToken() || ''
|
||||
};
|
||||
|
||||
// If it's a number, we use that as the expiration time (in seconds)
|
||||
if (options.auth === +options.auth) {
|
||||
authObject.expiration = FS.HTTP.now() + options.auth * 1000;
|
||||
}
|
||||
|
||||
// Set the authToken
|
||||
var authString = JSON.stringify(authObject);
|
||||
authToken = FS.Utility.btoa(authString);
|
||||
}
|
||||
} else if (typeof options.auth === "string") {
|
||||
// If the user supplies auth token the user will be responsible for
|
||||
// updating
|
||||
authToken = options.auth;
|
||||
}
|
||||
|
||||
// Construct query string
|
||||
var params = {};
|
||||
if (authToken !== '') {
|
||||
params.token = authToken;
|
||||
}
|
||||
if (options.download) {
|
||||
params.download = true;
|
||||
}
|
||||
if (options.store) {
|
||||
// We use options.store here instead of storeName because we want to omit the queryString
|
||||
// whenever possible, allowing users to have "clean" URLs if they want. The server will
|
||||
// assume the first store defined on the server, which means that we are assuming that
|
||||
// the first on the client is also the first on the server. If that's not the case, the
|
||||
// store option should be supplied.
|
||||
params.store = options.store;
|
||||
}
|
||||
var queryString = FS.Utility.encodeParams(params);
|
||||
if (queryString.length) {
|
||||
queryString = '?' + queryString;
|
||||
}
|
||||
|
||||
// Determine which URL to use
|
||||
var area;
|
||||
if (options.metadata) {
|
||||
area = '/record';
|
||||
} else {
|
||||
area = '/files';
|
||||
}
|
||||
|
||||
// Construct and return the http method url
|
||||
return baseUrl + area + '/' + self.collection.name + '/' + self._id + filename + queryString;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.File.prototype.url Construct the file url
|
||||
* @public
|
||||
* @param {Object} [options]
|
||||
* @param {String} [options.store] Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
|
||||
* @param {Boolean} [options.auth=null] Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
|
||||
* @param {Boolean} [options.download=false] Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
|
||||
* @param {Boolean} [options.brokenIsFine=false] Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
|
||||
* @param {Boolean} [options.metadata=false] Return the URL for the file metadata access point rather than the file itself.
|
||||
* @param {String} [options.uploading=null] A URL to return while the file is being uploaded.
|
||||
* @param {String} [options.storing=null] A URL to return while the file is being stored.
|
||||
* @param {String} [options.filename=null] Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
|
||||
*
|
||||
* Returns the HTTP URL for getting the file or its metadata.
|
||||
*/
|
||||
FS.File.prototype.url = function(options) {
|
||||
self = this;
|
||||
return rootUrlPathPrefix + self.urlRelative(options);
|
||||
};
|
||||
307
packages/wekan-cfs-access-point/access-point-handlers.js
Normal file
307
packages/wekan-cfs-access-point/access-point-handlers.js
Normal file
|
|
@ -0,0 +1,307 @@
|
|||
getHeaders = [];
|
||||
getHeadersByCollection = {};
|
||||
|
||||
var contentDisposition = Npm.require('content-disposition');
|
||||
|
||||
FS.HTTP.Handlers = {};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.Del
|
||||
* @public
|
||||
* @returns {any} response
|
||||
*
|
||||
* HTTP DEL request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.Del = function httpDelHandler(ref) {
|
||||
var self = this;
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
|
||||
|
||||
// If DELETE request, validate with 'remove' allow/deny, delete the file, and return
|
||||
FS.Utility.validateAction(ref.collection.files._validators['remove'], ref.file, self.userId);
|
||||
|
||||
/*
|
||||
* From the DELETE spec:
|
||||
* A successful response SHOULD be 200 (OK) if the response includes an
|
||||
* entity describing the status, 202 (Accepted) if the action has not
|
||||
* yet been enacted, or 204 (No Content) if the action has been enacted
|
||||
* but the response does not include an entity.
|
||||
*/
|
||||
self.setStatusCode(200);
|
||||
|
||||
return {
|
||||
deleted: !!ref.file.remove()
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.GetList
|
||||
* @public
|
||||
* @returns {Object} response
|
||||
*
|
||||
* HTTP GET file list request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.GetList = function httpGetListHandler() {
|
||||
// Not Yet Implemented
|
||||
// Need to check publications and return file list based on
|
||||
// what user is allowed to see
|
||||
};
|
||||
|
||||
/*
|
||||
requestRange will parse the range set in request header - if not possible it
|
||||
will throw fitting errors and autofill range for both partial and full ranges
|
||||
|
||||
throws error or returns the object:
|
||||
{
|
||||
start
|
||||
end
|
||||
length
|
||||
unit
|
||||
partial
|
||||
}
|
||||
*/
|
||||
var requestRange = function(req, fileSize) {
|
||||
if (req) {
|
||||
if (req.headers) {
|
||||
var rangeString = req.headers.range;
|
||||
|
||||
// Make sure range is a string
|
||||
if (rangeString === ''+rangeString) {
|
||||
|
||||
// range will be in the format "bytes=0-32767"
|
||||
var parts = rangeString.split('=');
|
||||
var unit = parts[0];
|
||||
|
||||
// Make sure parts consists of two strings and range is of type "byte"
|
||||
if (parts.length == 2 && unit == 'bytes') {
|
||||
// Parse the range
|
||||
var range = parts[1].split('-');
|
||||
var start = Number(range[0]);
|
||||
var end = Number(range[1]);
|
||||
|
||||
// Fix invalid ranges?
|
||||
if (range[0] != start) start = 0;
|
||||
if (range[1] != end || !end) end = fileSize - 1;
|
||||
|
||||
// Make sure range consists of a start and end point of numbers and start is less than end
|
||||
if (start < end) {
|
||||
|
||||
var partSize = 0 - start + end + 1;
|
||||
|
||||
// Return the parsed range
|
||||
return {
|
||||
start: start,
|
||||
end: end,
|
||||
length: partSize,
|
||||
size: fileSize,
|
||||
unit: unit,
|
||||
partial: (partSize < fileSize)
|
||||
};
|
||||
|
||||
} else {
|
||||
throw new Meteor.Error(416, "Requested Range Not Satisfiable");
|
||||
}
|
||||
|
||||
} else {
|
||||
// The first part should be bytes
|
||||
throw new Meteor.Error(416, "Requested Range Unit Not Satisfiable");
|
||||
}
|
||||
|
||||
} else {
|
||||
// No range found
|
||||
}
|
||||
|
||||
} else {
|
||||
// throw new Error('No request headers set for _parseRange function');
|
||||
}
|
||||
} else {
|
||||
throw new Error('No request object passed to _parseRange function');
|
||||
}
|
||||
|
||||
return {
|
||||
start: 0,
|
||||
end: fileSize - 1,
|
||||
length: fileSize,
|
||||
size: fileSize,
|
||||
unit: 'bytes',
|
||||
partial: false
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.Get
|
||||
* @public
|
||||
* @returns {any} response
|
||||
*
|
||||
* HTTP GET request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.Get = function httpGetHandler(ref) {
|
||||
var self = this;
|
||||
// Once we have the file, we can test allow/deny validators
|
||||
// XXX: pass on the "share" query eg. ?share=342hkjh23ggj for shared url access?
|
||||
FS.Utility.validateAction(ref.collection._validators['download'], ref.file, self.userId /*, self.query.shareId*/);
|
||||
|
||||
var storeName = ref.storeName;
|
||||
|
||||
// If no storeName was specified, use the first defined storeName
|
||||
if (typeof storeName !== "string") {
|
||||
// No store handed, we default to primary store
|
||||
storeName = ref.collection.primaryStore.name;
|
||||
}
|
||||
|
||||
// Get the storage reference
|
||||
var storage = ref.collection.storesLookup[storeName];
|
||||
|
||||
if (!storage) {
|
||||
throw new Meteor.Error(404, "Not Found", 'There is no store "' + storeName + '"');
|
||||
}
|
||||
|
||||
// Get the file
|
||||
var copyInfo = ref.file.copies[storeName];
|
||||
|
||||
if (!copyInfo) {
|
||||
throw new Meteor.Error(404, "Not Found", 'This file was not stored in the ' + storeName + ' store');
|
||||
}
|
||||
|
||||
// Set the content type for file
|
||||
if (typeof copyInfo.type === "string") {
|
||||
self.setContentType(copyInfo.type);
|
||||
} else {
|
||||
self.setContentType('application/octet-stream');
|
||||
}
|
||||
|
||||
// Add 'Content-Disposition' header if requested a download/attachment URL
|
||||
if (typeof ref.download !== "undefined") {
|
||||
var filename = ref.filename || copyInfo.name;
|
||||
self.addHeader('Content-Disposition', contentDisposition(filename));
|
||||
} else {
|
||||
self.addHeader('Content-Disposition', 'inline');
|
||||
}
|
||||
|
||||
// Get the contents range from request
|
||||
var range = requestRange(self.request, copyInfo.size);
|
||||
|
||||
// Some browsers cope better if the content-range header is
|
||||
// still included even for the full file being returned.
|
||||
self.addHeader('Content-Range', range.unit + ' ' + range.start + '-' + range.end + '/' + range.size);
|
||||
|
||||
// If a chunk/range was requested instead of the whole file, serve that'
|
||||
if (range.partial) {
|
||||
self.setStatusCode(206, 'Partial Content');
|
||||
} else {
|
||||
self.setStatusCode(200, 'OK');
|
||||
}
|
||||
|
||||
// Add any other global custom headers and collection-specific custom headers
|
||||
FS.Utility.each(getHeaders.concat(getHeadersByCollection[ref.collection.name] || []), function(header) {
|
||||
self.addHeader(header[0], header[1]);
|
||||
});
|
||||
|
||||
// Inform clients about length (or chunk length in case of ranges)
|
||||
self.addHeader('Content-Length', range.length);
|
||||
|
||||
// Last modified header (updatedAt from file info)
|
||||
self.addHeader('Last-Modified', copyInfo.updatedAt.toUTCString());
|
||||
|
||||
// Inform clients that we accept ranges for resumable chunked downloads
|
||||
self.addHeader('Accept-Ranges', range.unit);
|
||||
|
||||
if (FS.debug) console.log('Read file "' + (ref.filename || copyInfo.name) + '" ' + range.unit + ' ' + range.start + '-' + range.end + '/' + range.size);
|
||||
|
||||
var readStream = storage.adapter.createReadStream(ref.file, {start: range.start, end: range.end});
|
||||
|
||||
readStream.on('error', function(err) {
|
||||
// Send proper error message on get error
|
||||
if (err.message && err.statusCode) {
|
||||
self.Error(new Meteor.Error(err.statusCode, err.message));
|
||||
} else {
|
||||
self.Error(new Meteor.Error(503, 'Service unavailable'));
|
||||
}
|
||||
});
|
||||
|
||||
readStream.pipe(self.createWriteStream());
|
||||
};
|
||||
|
||||
// File with unicode or other encodings filename can upload to server susscessfully,
|
||||
// but when download, the HTTP header "Content-Disposition" cannot accept
|
||||
// characters other than ASCII, the filename should be converted to binary or URI encoded.
|
||||
// https://github.com/wekan/wekan/issues/784
|
||||
const originalHandler = FS.HTTP.Handlers.Get;
|
||||
FS.HTTP.Handlers.Get = function (ref) {
|
||||
try {
|
||||
var userAgent = (this.requestHeaders['user-agent']||'').toLowerCase();
|
||||
if(userAgent.indexOf('msie') >= 0 || userAgent.indexOf('chrome') >= 0) {
|
||||
ref.filename = encodeURIComponent(ref.filename);
|
||||
} else if(userAgent.indexOf('firefox') >= 0) {
|
||||
ref.filename = new Buffer(ref.filename).toString('binary');
|
||||
} else {
|
||||
/* safari*/
|
||||
ref.filename = new Buffer(ref.filename).toString('binary');
|
||||
}
|
||||
} catch (ex){
|
||||
ref.filename = ref.filename;
|
||||
}
|
||||
return originalHandler.call(this, ref);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.PutInsert
|
||||
* @public
|
||||
* @returns {Object} response object with _id property
|
||||
*
|
||||
* HTTP PUT file insert request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) {
|
||||
var self = this;
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
|
||||
|
||||
FS.debug && console.log("HTTP PUT (insert) handler");
|
||||
|
||||
// Create the nice FS.File
|
||||
var fileObj = new FS.File();
|
||||
|
||||
// Set its name
|
||||
fileObj.name(opts.filename || null);
|
||||
|
||||
// Attach the readstream as the file's data
|
||||
fileObj.attachData(self.createReadStream(), {type: self.requestHeaders['content-type'] || 'application/octet-stream'});
|
||||
|
||||
// Validate with insert allow/deny
|
||||
FS.Utility.validateAction(ref.collection.files._validators['insert'], fileObj, self.userId);
|
||||
|
||||
// Insert file into collection, triggering readStream storage
|
||||
ref.collection.insert(fileObj);
|
||||
|
||||
// Send response
|
||||
self.setStatusCode(200);
|
||||
|
||||
// Return the new file id
|
||||
return {_id: fileObj._id};
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.Handlers.PutUpdate
|
||||
* @public
|
||||
* @returns {Object} response object with _id and chunk properties
|
||||
*
|
||||
* HTTP PUT file update chunk request handler
|
||||
*/
|
||||
FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) {
|
||||
var self = this;
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
|
||||
|
||||
var chunk = parseInt(opts.chunk, 10);
|
||||
if (isNaN(chunk)) chunk = 0;
|
||||
|
||||
FS.debug && console.log("HTTP PUT (update) handler received chunk: ", chunk);
|
||||
|
||||
// Validate with insert allow/deny; also mounts and retrieves the file
|
||||
FS.Utility.validateAction(ref.collection.files._validators['insert'], ref.file, self.userId);
|
||||
|
||||
self.createReadStream().pipe( FS.TempStore.createWriteStream(ref.file, chunk) );
|
||||
|
||||
// Send response
|
||||
self.setStatusCode(200);
|
||||
|
||||
return { _id: ref.file._id, chunk: chunk };
|
||||
};
|
||||
362
packages/wekan-cfs-access-point/access-point-server.js
Normal file
362
packages/wekan-cfs-access-point/access-point-server.js
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
var path = Npm.require("path");
|
||||
|
||||
HTTP.publishFormats({
|
||||
fileRecordFormat: function (input) {
|
||||
// Set the method scope content type to json
|
||||
this.setContentType('application/json');
|
||||
if (FS.Utility.isArray(input)) {
|
||||
return EJSON.stringify(FS.Utility.map(input, function (obj) {
|
||||
return FS.Utility.cloneFileRecord(obj);
|
||||
}));
|
||||
} else {
|
||||
return EJSON.stringify(FS.Utility.cloneFileRecord(input));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.setHeadersForGet
|
||||
* @public
|
||||
* @param {Array} headers - List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
|
||||
* @param {Array|String} [collections] - Which collections the headers should be added for. Omit this argument to add the header for all collections.
|
||||
* @returns {undefined}
|
||||
*/
|
||||
FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) {
|
||||
if (typeof collections === "string") {
|
||||
collections = [collections];
|
||||
}
|
||||
if (collections) {
|
||||
FS.Utility.each(collections, function(collectionName) {
|
||||
getHeadersByCollection[collectionName] = headers || [];
|
||||
});
|
||||
} else {
|
||||
getHeaders = headers || [];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.publish
|
||||
* @public
|
||||
* @param {FS.Collection} collection
|
||||
* @param {Function} func - Publish function that returns a cursor.
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Publishes all documents returned by the cursor at a GET URL
|
||||
* with the format baseUrl/record/collectionName. The publish
|
||||
* function `this` is similar to normal `Meteor.publish`.
|
||||
*/
|
||||
FS.HTTP.publish = function fsHttpPublish(collection, func) {
|
||||
var name = baseUrl + '/record/' + collection.name;
|
||||
// Mount collection listing URL using http-publish package
|
||||
HTTP.publish({
|
||||
name: name,
|
||||
defaultFormat: 'fileRecordFormat',
|
||||
collection: collection,
|
||||
collectionGet: true,
|
||||
collectionPost: false,
|
||||
documentGet: true,
|
||||
documentPut: false,
|
||||
documentDelete: false
|
||||
}, func);
|
||||
|
||||
FS.debug && console.log("Registered HTTP method GET URLs:\n\n" + name + '\n' + name + '/:id\n');
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.unpublish
|
||||
* @public
|
||||
* @param {FS.Collection} collection
|
||||
* @returns {undefined}
|
||||
*
|
||||
* Unpublishes a restpoint created by a call to `FS.HTTP.publish`
|
||||
*/
|
||||
FS.HTTP.unpublish = function fsHttpUnpublish(collection) {
|
||||
// Mount collection listing URL using http-publish package
|
||||
HTTP.unpublish(baseUrl + '/record/' + collection.name);
|
||||
};
|
||||
|
||||
_existingMountPoints = {};
|
||||
|
||||
/**
|
||||
* @method defaultSelectorFunction
|
||||
* @private
|
||||
* @returns { collection, file }
|
||||
*
|
||||
* This is the default selector function
|
||||
*/
|
||||
var defaultSelectorFunction = function() {
|
||||
var self = this;
|
||||
// Selector function
|
||||
//
|
||||
// This function will have to return the collection and the
|
||||
// file. If file not found undefined is returned - if null is returned the
|
||||
// search was not possible
|
||||
var opts = FS.Utility.extend({}, self.query || {}, self.params || {});
|
||||
|
||||
// Get the collection name from the url
|
||||
var collectionName = opts.collectionName;
|
||||
|
||||
// Get the id from the url
|
||||
var id = opts.id;
|
||||
|
||||
// Get the collection
|
||||
var collection = FS._collections[collectionName];
|
||||
|
||||
//if Mongo ObjectIds are used, then we need to use that in find statement
|
||||
if(collection.options.idGeneration && collection.options.idGeneration === 'MONGO') {
|
||||
// Get the file if possible else return null
|
||||
var file = (id && collection)? collection.findOne({ _id: new Meteor.Collection.ObjectID(id)}): null;
|
||||
} else {
|
||||
var file = (id && collection)? collection.findOne({ _id: id }): null;
|
||||
}
|
||||
|
||||
|
||||
// Return the collection and the file
|
||||
return {
|
||||
collection: collection,
|
||||
file: file,
|
||||
storeName: opts.store,
|
||||
download: opts.download,
|
||||
filename: opts.filename
|
||||
};
|
||||
};
|
||||
|
||||
/*
|
||||
* @method FS.HTTP.mount
|
||||
* @public
|
||||
* @param {array of string} mountPoints mount points to map rest functinality on
|
||||
* @param {function} selector_f [selector] function returns `{ collection, file }` for mount points to work with
|
||||
*
|
||||
*/
|
||||
FS.HTTP.mount = function(mountPoints, selector_f) {
|
||||
// We take mount points as an array and we get a selector function
|
||||
var selectorFunction = selector_f || defaultSelectorFunction;
|
||||
|
||||
var accessPoint = {
|
||||
'stream': true,
|
||||
'auth': expirationAuth,
|
||||
'post': function(data) {
|
||||
// Use the selector for finding the collection and file reference
|
||||
var ref = selectorFunction.call(this);
|
||||
|
||||
// We dont support post - this would be normal insert eg. of filerecord?
|
||||
throw new Meteor.Error(501, "Not implemented", "Post is not supported");
|
||||
},
|
||||
'put': function(data) {
|
||||
// Use the selector for finding the collection and file reference
|
||||
var ref = selectorFunction.call(this);
|
||||
|
||||
// Make sure we have a collection reference
|
||||
if (!ref.collection)
|
||||
throw new Meteor.Error(404, "Not Found", "No collection found");
|
||||
|
||||
// Make sure we have a file reference
|
||||
if (ref.file === null) {
|
||||
// No id supplied so we will create a new FS.File instance and
|
||||
// insert the supplied data.
|
||||
return FS.HTTP.Handlers.PutInsert.apply(this, [ref]);
|
||||
} else {
|
||||
if (ref.file) {
|
||||
return FS.HTTP.Handlers.PutUpdate.apply(this, [ref]);
|
||||
} else {
|
||||
throw new Meteor.Error(404, "Not Found", 'No file found');
|
||||
}
|
||||
}
|
||||
},
|
||||
'get': function(data) {
|
||||
// Use the selector for finding the collection and file reference
|
||||
var ref = selectorFunction.call(this);
|
||||
|
||||
// Make sure we have a collection reference
|
||||
if (!ref.collection)
|
||||
throw new Meteor.Error(404, "Not Found", "No collection found");
|
||||
|
||||
// Make sure we have a file reference
|
||||
if (ref.file === null) {
|
||||
// No id supplied so we will return the published list of files ala
|
||||
// http.publish in json format
|
||||
return FS.HTTP.Handlers.GetList.apply(this, [ref]);
|
||||
} else {
|
||||
if (ref.file) {
|
||||
return FS.HTTP.Handlers.Get.apply(this, [ref]);
|
||||
} else {
|
||||
throw new Meteor.Error(404, "Not Found", 'No file found');
|
||||
}
|
||||
}
|
||||
},
|
||||
'delete': function(data) {
|
||||
// Use the selector for finding the collection and file reference
|
||||
var ref = selectorFunction.call(this);
|
||||
|
||||
// Make sure we have a collection reference
|
||||
if (!ref.collection)
|
||||
throw new Meteor.Error(404, "Not Found", "No collection found");
|
||||
|
||||
// Make sure we have a file reference
|
||||
if (ref.file) {
|
||||
return FS.HTTP.Handlers.Del.apply(this, [ref]);
|
||||
} else {
|
||||
throw new Meteor.Error(404, "Not Found", 'No file found');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var accessPoints = {};
|
||||
|
||||
// Add debug message
|
||||
FS.debug && console.log('Registered HTTP method URLs:');
|
||||
|
||||
FS.Utility.each(mountPoints, function(mountPoint) {
|
||||
// Couple mountpoint and accesspoint
|
||||
accessPoints[mountPoint] = accessPoint;
|
||||
// Remember our mountpoints
|
||||
_existingMountPoints[mountPoint] = mountPoint;
|
||||
// Add debug message
|
||||
FS.debug && console.log(mountPoint);
|
||||
});
|
||||
|
||||
// XXX: HTTP:methods should unmount existing mounts in case of overwriting?
|
||||
HTTP.methods(accessPoints);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @method FS.HTTP.unmount
|
||||
* @public
|
||||
* @param {string | array of string} [mountPoints] Optional, if not specified all mountpoints are unmounted
|
||||
*
|
||||
*/
|
||||
FS.HTTP.unmount = function(mountPoints) {
|
||||
// The mountPoints is optional, can be string or array if undefined then
|
||||
// _existingMountPoints will be used
|
||||
var unmountList;
|
||||
// Container for the mount points to unmount
|
||||
var unmountPoints = {};
|
||||
|
||||
if (typeof mountPoints === 'undefined') {
|
||||
// Use existing mount points - unmount all
|
||||
unmountList = _existingMountPoints;
|
||||
} else if (mountPoints === ''+mountPoints) {
|
||||
// Got a string
|
||||
unmountList = [mountPoints];
|
||||
} else if (mountPoints.length) {
|
||||
// Got an array
|
||||
unmountList = mountPoints;
|
||||
}
|
||||
|
||||
// If we have a list to unmount
|
||||
if (unmountList) {
|
||||
// Iterate over each item
|
||||
FS.Utility.each(unmountList, function(mountPoint) {
|
||||
// Check _existingMountPoints to make sure the mount point exists in our
|
||||
// context / was created by the FS.HTTP.mount
|
||||
if (_existingMountPoints[mountPoint]) {
|
||||
// Mark as unmount
|
||||
unmountPoints[mountPoint] = false;
|
||||
// Release
|
||||
delete _existingMountPoints[mountPoint];
|
||||
}
|
||||
});
|
||||
FS.debug && console.log('FS.HTTP.unmount:');
|
||||
FS.debug && console.log(unmountPoints);
|
||||
// Complete unmount
|
||||
HTTP.methods(unmountPoints);
|
||||
}
|
||||
};
|
||||
|
||||
// ### FS.Collection maps on HTTP pr. default on the following restpoints:
|
||||
// *
|
||||
// baseUrl + '/files/:collectionName/:id/:filename',
|
||||
// baseUrl + '/files/:collectionName/:id',
|
||||
// baseUrl + '/files/:collectionName'
|
||||
//
|
||||
// Change/ replace the existing mount point by:
|
||||
// ```js
|
||||
// // unmount all existing
|
||||
// FS.HTTP.unmount();
|
||||
// // Create new mount point
|
||||
// FS.HTTP.mount([
|
||||
// '/cfs/files/:collectionName/:id/:filename',
|
||||
// '/cfs/files/:collectionName/:id',
|
||||
// '/cfs/files/:collectionName'
|
||||
// ]);
|
||||
// ```
|
||||
//
|
||||
mountUrls = function mountUrls() {
|
||||
// We unmount first in case we are calling this a second time
|
||||
FS.HTTP.unmount();
|
||||
|
||||
FS.HTTP.mount([
|
||||
baseUrl + '/files/:collectionName/:id/:filename',
|
||||
baseUrl + '/files/:collectionName/:id',
|
||||
baseUrl + '/files/:collectionName'
|
||||
]);
|
||||
};
|
||||
|
||||
// Returns the userId from URL token
|
||||
var expirationAuth = function expirationAuth() {
|
||||
var self = this;
|
||||
|
||||
// Read the token from '/hello?token=base64'
|
||||
var encodedToken = self.query.token;
|
||||
|
||||
FS.debug && console.log("token: "+encodedToken);
|
||||
|
||||
if (!encodedToken || !Meteor.users) return false;
|
||||
|
||||
// Check the userToken before adding it to the db query
|
||||
// Set the this.userId
|
||||
var tokenString = FS.Utility.atob(encodedToken);
|
||||
|
||||
var tokenObject;
|
||||
try {
|
||||
tokenObject = JSON.parse(tokenString);
|
||||
} catch(err) {
|
||||
throw new Meteor.Error(400, 'Bad Request');
|
||||
}
|
||||
|
||||
// XXX: Do some check here of the object
|
||||
var userToken = tokenObject.authToken;
|
||||
if (userToken !== ''+userToken) {
|
||||
throw new Meteor.Error(400, 'Bad Request');
|
||||
}
|
||||
|
||||
// If we have an expiration token we should check that it's still valid
|
||||
if (tokenObject.expiration != null) {
|
||||
// check if its too old
|
||||
var now = Date.now();
|
||||
if (tokenObject.expiration < now) {
|
||||
FS.debug && console.log('Expired token: ' + tokenObject.expiration + ' is less than ' + now);
|
||||
throw new Meteor.Error(500, 'Expired token');
|
||||
}
|
||||
}
|
||||
|
||||
// We are not on a secure line - so we have to look up the user...
|
||||
var user = Meteor.users.findOne({
|
||||
$or: [
|
||||
{'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(userToken)},
|
||||
{'services.resume.loginTokens.token': userToken}
|
||||
]
|
||||
});
|
||||
|
||||
// Set the userId in the scope
|
||||
return user && user._id;
|
||||
};
|
||||
|
||||
HTTP.methods(
|
||||
{'/cfs/servertime': {
|
||||
get: function(data) {
|
||||
return Date.now().toString();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Unify client / server api
|
||||
FS.HTTP.now = function() {
|
||||
return Date.now();
|
||||
};
|
||||
|
||||
// Start up the basic mount points
|
||||
Meteor.startup(function () {
|
||||
mountUrls();
|
||||
});
|
||||
271
packages/wekan-cfs-access-point/api.md
Normal file
271
packages/wekan-cfs-access-point/api.md
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
## wekan-cfs-access-point Public API ##
|
||||
|
||||
CollectionFS, add ddp and http accesspoint capability
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.setBaseUrl"></a>*FSHTTP*.setBaseUrl(newBaseUrl) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __setBaseUrl__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __newBaseUrl__ *{String}*
|
||||
|
||||
Change the base URL for the HTTP GET and DELETE endpoints.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) { ...``` [access-point-common.js:29](access-point-common.js#L29)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.url"></a>*fsFile*.url([options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __url__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{String}* (Optional)
|
||||
|
||||
Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
|
||||
|
||||
* __auth__ *{Boolean}* (Optional, Default = null)
|
||||
|
||||
Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
|
||||
|
||||
* __download__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
|
||||
|
||||
* __brokenIsFine__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
|
||||
|
||||
* __metadata__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Return the URL for the file metadata access point rather than the file itself.
|
||||
|
||||
* __uploading__ *{String}* (Optional, Default = null)
|
||||
|
||||
A URL to return while the file is being uploaded.
|
||||
|
||||
* __storing__ *{String}* (Optional, Default = null)
|
||||
|
||||
A URL to return while the file is being stored.
|
||||
|
||||
* __filename__ *{String}* (Optional, Default = null)
|
||||
|
||||
Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
|
||||
|
||||
|
||||
|
||||
Returns the HTTP URL for getting the file or its metadata.
|
||||
|
||||
> ```FS.File.prototype.url = function(options) { ...``` [access-point-common.js:72](access-point-common.js#L72)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.Del"></a>*FSHTTPHandlers*.Del() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Del__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{any}*
|
||||
response
|
||||
|
||||
|
||||
HTTP DEL request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.Del = function httpDelHandler(ref) { ...``` [access-point-handlers.js:13](access-point-handlers.js#L13)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.GetList"></a>*FSHTTPHandlers*.GetList() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __GetList__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response
|
||||
|
||||
|
||||
HTTP GET file list request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.GetList = function httpGetListHandler() { ...``` [access-point-handlers.js:41](access-point-handlers.js#L41)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.Get"></a>*FSHTTPHandlers*.Get() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Get__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{any}*
|
||||
response
|
||||
|
||||
|
||||
HTTP GET request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.Get = function httpGetHandler(ref) { ...``` [access-point-handlers.js:135](access-point-handlers.js#L135)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.PutInsert"></a>*FSHTTPHandlers*.PutInsert() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __PutInsert__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response object with _id property
|
||||
|
||||
|
||||
HTTP PUT file insert request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) { ...``` [access-point-handlers.js:229](access-point-handlers.js#L229)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.PutUpdate"></a>*FSHTTPHandlers*.PutUpdate() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __PutUpdate__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response object with _id and chunk properties
|
||||
|
||||
|
||||
HTTP PUT file update chunk request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { ...``` [access-point-handlers.js:264](access-point-handlers.js#L264)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.setHeadersForGet"></a>*FSHTTP*.setHeadersForGet(headers, [collections]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __setHeadersForGet__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __headers__ *{Array}*
|
||||
|
||||
List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
|
||||
|
||||
* __collections__ *{Array|String}* (Optional)
|
||||
|
||||
Which collections the headers should be added for. Omit this argument to add the header for all collections.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) { ...``` [access-point-server.js:24](access-point-server.js#L24)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.publish"></a>*FSHTTP*.publish(collection, func) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __publish__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __collection__ *{[FS.Collection](#FS.Collection)}*
|
||||
* __func__ *{Function}*
|
||||
|
||||
Publish function that returns a cursor.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Publishes all documents returned by the cursor at a GET URL
|
||||
with the format baseUrl/record/collectionName. The publish
|
||||
function `this` is similar to normal `Meteor.publish`.
|
||||
|
||||
> ```FS.HTTP.publish = function fsHttpPublish(collection, func) { ...``` [access-point-server.js:48](access-point-server.js#L48)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.unpublish"></a>*FSHTTP*.unpublish(collection) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __unpublish__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __collection__ *{[FS.Collection](#FS.Collection)}*
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Unpublishes a restpoint created by a call to `FS.HTTP.publish`
|
||||
|
||||
> ```FS.HTTP.unpublish = function fsHttpUnpublish(collection) { ...``` [access-point-server.js:73](access-point-server.js#L73)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.mount"></a>*FSHTTP*.mount(mountPoints, selector_f) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __mount__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __mountPoints__ *{[array of string](#array of string)}*
|
||||
|
||||
mount points to map rest functinality on
|
||||
|
||||
* __selector_f__ *{function}*
|
||||
|
||||
[selector] function returns `{ collection, file }` for mount points to work with
|
||||
|
||||
|
||||
|
||||
> ```FS.HTTP.mount = function(mountPoints, selector_f) { ...``` [access-point-server.js:125](access-point-server.js#L125)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.unmount"></a>*FSHTTP*.unmount([mountPoints]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __unmount__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __mountPoints__ *{[string ](#string )|[ array of string](# array of string)}* (Optional)
|
||||
|
||||
Optional, if not specified all mountpoints are unmounted
|
||||
|
||||
|
||||
|
||||
|
||||
> ```FS.HTTP.unmount = function(mountPoints) { ...``` [access-point-server.js:223](access-point-server.js#L223)
|
||||
|
||||
|
||||
|
||||
-
|
||||
### FS.Collection maps on HTTP pr. default on the following restpoints:
|
||||
*
|
||||
baseUrl + '/files/:collectionName/:id/:filename',
|
||||
baseUrl + '/files/:collectionName/:id',
|
||||
baseUrl + '/files/:collectionName'
|
||||
|
||||
Change/ replace the existing mount point by:
|
||||
```js
|
||||
unmount all existing
|
||||
FS.HTTP.unmount();
|
||||
Create new mount point
|
||||
FS.HTTP.mount([
|
||||
'/cfs/files/:collectionName/:id/:filename',
|
||||
'/cfs/files/:collectionName/:id',
|
||||
'/cfs/files/:collectionName'
|
||||
]);
|
||||
```
|
||||
332
packages/wekan-cfs-access-point/internal.api.md
Normal file
332
packages/wekan-cfs-access-point/internal.api.md
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
## Public and Private API ##
|
||||
|
||||
_API documentation automatically generated by [docmeteor](https://github.com/raix/docmeteor)._
|
||||
|
||||
***
|
||||
|
||||
__File: ["access-point-common.js"](access-point-common.js) Where: {server|client}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.HTTP.setBaseUrl"></a>*FSHTTP*.setBaseUrl(newBaseUrl) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __setBaseUrl__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __newBaseUrl__ *{String}*
|
||||
|
||||
Change the base URL for the HTTP GET and DELETE endpoints.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.HTTP.setBaseUrl = function setBaseUrl(newBaseUrl) { ...``` [access-point-common.js:29](access-point-common.js#L29)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.File.prototype.url"></a>*fsFile*.url([options]) <sub><i>Anywhere</i></sub> ###
|
||||
|
||||
*This method __url__ is defined in `prototype` of `FS.File`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __options__ *{Object}* (Optional)
|
||||
* __store__ *{String}* (Optional)
|
||||
|
||||
Name of the store to get from. If not defined, the first store defined in `options.stores` for the collection on the client is used.
|
||||
|
||||
* __auth__ *{Boolean}* (Optional, Default = null)
|
||||
|
||||
Add authentication token to the URL query string? By default, a token for the current logged in user is added on the client. Set this to `false` to omit the token. Set this to a string to provide your own token. Set this to a number to specify an expiration time for the token in seconds.
|
||||
|
||||
* __download__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Should headers be set to force a download? Typically this means that clicking the link with this URL will download the file to the user's Downloads folder instead of displaying the file in the browser.
|
||||
|
||||
* __brokenIsFine__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Return the URL even if we know it's currently a broken link because the file hasn't been saved in the requested store yet.
|
||||
|
||||
* __metadata__ *{Boolean}* (Optional, Default = false)
|
||||
|
||||
Return the URL for the file metadata access point rather than the file itself.
|
||||
|
||||
* __uploading__ *{String}* (Optional, Default = null)
|
||||
|
||||
A URL to return while the file is being uploaded.
|
||||
|
||||
* __storing__ *{String}* (Optional, Default = null)
|
||||
|
||||
A URL to return while the file is being stored.
|
||||
|
||||
* __filename__ *{String}* (Optional, Default = null)
|
||||
|
||||
Override the filename that should appear at the end of the URL. By default it is the name of the file in the requested store.
|
||||
|
||||
|
||||
|
||||
Returns the HTTP URL for getting the file or its metadata.
|
||||
|
||||
> ```FS.File.prototype.url = function(options) { ...``` [access-point-common.js:72](access-point-common.js#L72)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["access-point-handlers.js"](access-point-handlers.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.HTTP.Handlers.Del"></a>*FSHTTPHandlers*.Del() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Del__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{any}*
|
||||
response
|
||||
|
||||
|
||||
HTTP DEL request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.Del = function httpDelHandler(ref) { ...``` [access-point-handlers.js:13](access-point-handlers.js#L13)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="self.setStatusCode"></a>*self*.setStatusCode {any} <sub><i>Server</i></sub> ###
|
||||
|
||||
```
|
||||
From the DELETE spec:
|
||||
A successful response SHOULD be 200 (OK) if the response includes an
|
||||
entity describing the status, 202 (Accepted) if the action has not
|
||||
yet been enacted, or 204 (No Content) if the action has been enacted
|
||||
but the response does not include an entity.
|
||||
```
|
||||
*This property __setStatusCode__ is defined in `self`*
|
||||
|
||||
> ```self.setStatusCode(200);``` [access-point-handlers.js:27](access-point-handlers.js#L27)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.GetList"></a>*FSHTTPHandlers*.GetList() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __GetList__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response
|
||||
|
||||
|
||||
HTTP GET file list request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.GetList = function httpGetListHandler() { ...``` [access-point-handlers.js:41](access-point-handlers.js#L41)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="requestRange"></a>requestRange {any} <sub><i>Server</i></sub> ###
|
||||
|
||||
```
|
||||
requestRange will parse the range set in request header - if not possible it
|
||||
will throw fitting errors and autofill range for both partial and full ranges
|
||||
throws error or returns the object:
|
||||
{
|
||||
start
|
||||
end
|
||||
length
|
||||
unit
|
||||
partial
|
||||
}
|
||||
```
|
||||
*This property is private*
|
||||
|
||||
> ```var requestRange = function(req, fileSize) { ...``` [access-point-handlers.js:60](access-point-handlers.js#L60)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.Get"></a>*FSHTTPHandlers*.Get() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __Get__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{any}*
|
||||
response
|
||||
|
||||
|
||||
HTTP GET request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.Get = function httpGetHandler(ref) { ...``` [access-point-handlers.js:135](access-point-handlers.js#L135)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.PutInsert"></a>*FSHTTPHandlers*.PutInsert() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __PutInsert__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response object with _id property
|
||||
|
||||
|
||||
HTTP PUT file insert request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.PutInsert = function httpPutInsertHandler(ref) { ...``` [access-point-handlers.js:229](access-point-handlers.js#L229)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.Handlers.PutUpdate"></a>*FSHTTPHandlers*.PutUpdate() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __PutUpdate__ is defined in `FS.HTTP.Handlers`*
|
||||
|
||||
__Returns__ *{Object}*
|
||||
response object with _id and chunk properties
|
||||
|
||||
|
||||
HTTP PUT file update chunk request handler
|
||||
|
||||
> ```FS.HTTP.Handlers.PutUpdate = function httpPutUpdateHandler(ref) { ...``` [access-point-handlers.js:264](access-point-handlers.js#L264)
|
||||
|
||||
|
||||
***
|
||||
|
||||
__File: ["access-point-server.js"](access-point-server.js) Where: {server}__
|
||||
|
||||
***
|
||||
|
||||
### <a name="FS.HTTP.setHeadersForGet"></a>*FSHTTP*.setHeadersForGet(headers, [collections]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __setHeadersForGet__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __headers__ *{Array}*
|
||||
|
||||
List of headers, where each is a two-item array in which item 1 is the header name and item 2 is the header value.
|
||||
|
||||
* __collections__ *{Array|String}* (Optional)
|
||||
|
||||
Which collections the headers should be added for. Omit this argument to add the header for all collections.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
> ```FS.HTTP.setHeadersForGet = function setHeadersForGet(headers, collections) { ...``` [access-point-server.js:24](access-point-server.js#L24)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.publish"></a>*FSHTTP*.publish(collection, func) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __publish__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __collection__ *{[FS.Collection](#FS.Collection)}*
|
||||
* __func__ *{Function}*
|
||||
|
||||
Publish function that returns a cursor.
|
||||
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Publishes all documents returned by the cursor at a GET URL
|
||||
with the format baseUrl/record/collectionName. The publish
|
||||
function `this` is similar to normal `Meteor.publish`.
|
||||
|
||||
> ```FS.HTTP.publish = function fsHttpPublish(collection, func) { ...``` [access-point-server.js:48](access-point-server.js#L48)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.unpublish"></a>*FSHTTP*.unpublish(collection) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __unpublish__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __collection__ *{[FS.Collection](#FS.Collection)}*
|
||||
|
||||
__Returns__ *{undefined}*
|
||||
|
||||
|
||||
Unpublishes a restpoint created by a call to `FS.HTTP.publish`
|
||||
|
||||
> ```FS.HTTP.unpublish = function fsHttpUnpublish(collection) { ...``` [access-point-server.js:73](access-point-server.js#L73)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="defaultSelectorFunction"></a>defaultSelectorFunction() <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method is private*
|
||||
|
||||
__Returns__ *{ collection, file }*
|
||||
|
||||
|
||||
This is the default selector function
|
||||
|
||||
> ```var defaultSelectorFunction = function() { ...``` [access-point-server.js:87](access-point-server.js#L87)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.mount"></a>*FSHTTP*.mount(mountPoints, selector_f) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __mount__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __mountPoints__ *{[array of string](#array of string)}*
|
||||
|
||||
mount points to map rest functinality on
|
||||
|
||||
* __selector_f__ *{function}*
|
||||
|
||||
[selector] function returns `{ collection, file }` for mount points to work with
|
||||
|
||||
|
||||
|
||||
> ```FS.HTTP.mount = function(mountPoints, selector_f) { ...``` [access-point-server.js:125](access-point-server.js#L125)
|
||||
|
||||
|
||||
-
|
||||
|
||||
### <a name="FS.HTTP.unmount"></a>*FSHTTP*.unmount([mountPoints]) <sub><i>Server</i></sub> ###
|
||||
|
||||
*This method __unmount__ is defined in `FS.HTTP`*
|
||||
|
||||
__Arguments__
|
||||
|
||||
* __mountPoints__ *{[string ](#string )|[ array of string](# array of string)}* (Optional)
|
||||
|
||||
Optional, if not specified all mountpoints are unmounted
|
||||
|
||||
|
||||
|
||||
|
||||
> ```FS.HTTP.unmount = function(mountPoints) { ...``` [access-point-server.js:223](access-point-server.js#L223)
|
||||
|
||||
|
||||
|
||||
-
|
||||
### FS.Collection maps on HTTP pr. default on the following restpoints:
|
||||
*
|
||||
baseUrl + '/files/:collectionName/:id/:filename',
|
||||
baseUrl + '/files/:collectionName/:id',
|
||||
baseUrl + '/files/:collectionName'
|
||||
|
||||
Change/ replace the existing mount point by:
|
||||
```js
|
||||
unmount all existing
|
||||
FS.HTTP.unmount();
|
||||
Create new mount point
|
||||
FS.HTTP.mount([
|
||||
'/cfs/files/:collectionName/:id/:filename',
|
||||
'/cfs/files/:collectionName/:id',
|
||||
'/cfs/files/:collectionName'
|
||||
]);
|
||||
```
|
||||
65
packages/wekan-cfs-access-point/package.js
Normal file
65
packages/wekan-cfs-access-point/package.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
Package.describe({
|
||||
name: 'wekan-cfs-access-point',
|
||||
version: '0.1.50',
|
||||
summary: 'CollectionFS, add ddp and http accesspoint capability',
|
||||
git: 'https://github.com/zcfs/Meteor-cfs-access-point.git'
|
||||
});
|
||||
|
||||
Npm.depends({
|
||||
"content-disposition": "0.5.0"
|
||||
});
|
||||
|
||||
Package.onUse(function(api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
// This imply is needed for tests, and is technically probably correct anyway.
|
||||
api.imply([
|
||||
'wekan-cfs-base-package'
|
||||
]);
|
||||
|
||||
api.use([
|
||||
//CFS packages
|
||||
'wekan-cfs-base-package@0.0.30',
|
||||
'wekan-cfs-file@0.1.16',
|
||||
//Core packages
|
||||
'check',
|
||||
'ejson',
|
||||
//Other packages
|
||||
'wekan-cfs-http-methods@0.0.29',
|
||||
'wekan-cfs-http-publish@0.0.13'
|
||||
]);
|
||||
|
||||
api.addFiles([
|
||||
'access-point-common.js',
|
||||
'access-point-handlers.js',
|
||||
'access-point-server.js'
|
||||
], 'server');
|
||||
|
||||
api.addFiles([
|
||||
'access-point-common.js',
|
||||
'access-point-client.js'
|
||||
], 'client');
|
||||
});
|
||||
|
||||
Package.onTest(function (api) {
|
||||
api.versionsFrom('1.0');
|
||||
|
||||
api.use([
|
||||
//CFS packages
|
||||
'wekan-cfs-access-point',
|
||||
'wekan-cfs-standard-packages@0.0.2',
|
||||
'wekan-cfs-gridfs@0.0.0',
|
||||
//Core packages
|
||||
'test-helpers',
|
||||
'http',
|
||||
'tinytest',
|
||||
'underscore',
|
||||
'ejson',
|
||||
'ordered-dict',
|
||||
'random',
|
||||
'deps'
|
||||
]);
|
||||
|
||||
api.addFiles('tests/client-tests.js', 'client');
|
||||
api.addFiles('tests/server-tests.js', 'server');
|
||||
});
|
||||
125
packages/wekan-cfs-access-point/tests/client-tests.js
Normal file
125
packages/wekan-cfs-access-point/tests/client-tests.js
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
Tinytest.add('cfs-access-point - client - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
test.isTrue(typeof FS.HTTP !== 'undefined', 'test environment not initialized FS.HTTP');
|
||||
});
|
||||
|
||||
Images = new FS.Collection('images', {
|
||||
stores: [
|
||||
new FS.Store.GridFS('gridList')
|
||||
]
|
||||
});
|
||||
|
||||
Meteor.subscribe("img");
|
||||
|
||||
var id;
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - addTestImage', function(test, onComplete) {
|
||||
Meteor.call('addTestImage', function(err, result) {
|
||||
id = result;
|
||||
test.equal(typeof id, "string", "Test image was not inserted properly");
|
||||
//Don't continue until the data has been stored
|
||||
Deps.autorun(function (c) {
|
||||
var img = Images.findOne(id);
|
||||
if (img && img.hasCopy('gridList')) {
|
||||
onComplete();
|
||||
c.stop();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - GET list of files in collection', function(test, onComplete) {
|
||||
|
||||
HTTP.get(Meteor.absoluteUrl('cfs/record/images'), 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._id, id, 'Didn\'t get the expected result');
|
||||
onComplete();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - GET filerecord', function(test, onComplete) {
|
||||
|
||||
HTTP.get(Meteor.absoluteUrl('cfs/record/images/' + id), function(err, result) {
|
||||
// Get the object
|
||||
var obj = result.data;
|
||||
test.equal(typeof obj, "object", "Expected object data");
|
||||
test.equal(obj._id, id, 'Didn\'t get the expected result');
|
||||
onComplete();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - GET file itself', function(test, onComplete) {
|
||||
|
||||
HTTP.get(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) {
|
||||
test.isTrue(!!result.content, "Expected content in response");
|
||||
console.log(result);
|
||||
test.equal(result.statusCode, 200, "Expected 200 OK response");
|
||||
onComplete();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - PUT new file data (update)', function(test, onComplete) {
|
||||
// TODO
|
||||
// HTTP.put(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) {
|
||||
// test.equal(result.statusCode, 200, "Expected 200 OK response");
|
||||
onComplete();
|
||||
// });
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - PUT insert a new file', function(test, onComplete) {
|
||||
// TODO
|
||||
// HTTP.put(Meteor.absoluteUrl('cfs/files/images'), function(err, result) {
|
||||
// test.equal(result.statusCode, 200, "Expected 200 OK response");
|
||||
onComplete();
|
||||
// });
|
||||
|
||||
});
|
||||
|
||||
Tinytest.addAsync('cfs-access-point - client - DELETE filerecord and data', function(test, onComplete) {
|
||||
|
||||
HTTP.del(Meteor.absoluteUrl('cfs/files/images/' + id), function(err, result) {
|
||||
test.equal(result.statusCode, 200, "Expected 200 OK response");
|
||||
|
||||
// Make sure it's gone
|
||||
HTTP.get(Meteor.absoluteUrl('cfs/record/images/' + id), function(err, result) {
|
||||
test.isTrue(!!err, 'Expected 404 error');
|
||||
test.equal(result.statusCode, 404, "Expected 404 response");
|
||||
onComplete();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
//TODO test FS.File.prototype.url method with various options
|
||||
|
||||
//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)
|
||||
68
packages/wekan-cfs-access-point/tests/server-tests.js
Normal file
68
packages/wekan-cfs-access-point/tests/server-tests.js
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
function equals(a, b) {
|
||||
return !!(EJSON.stringify(a) === EJSON.stringify(b));
|
||||
}
|
||||
|
||||
FS.debug = true;
|
||||
|
||||
Tinytest.add('cfs-access-point - server - test environment', function(test) {
|
||||
test.isTrue(typeof FS.Collection !== 'undefined', 'test environment not initialized FS.Collection');
|
||||
test.isTrue(typeof FS.HTTP !== 'undefined', 'test environment not initialized FS.HTTP');
|
||||
});
|
||||
|
||||
Images = new FS.Collection('images', {
|
||||
stores: [
|
||||
new FS.Store.GridFS('gridList')
|
||||
]
|
||||
});
|
||||
|
||||
Images.allow({
|
||||
insert: function() {
|
||||
return true;
|
||||
},
|
||||
update: function() {
|
||||
return true;
|
||||
},
|
||||
remove: function() {
|
||||
return true;
|
||||
},
|
||||
download: function() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
Meteor.publish("img", function () {
|
||||
return Images.find();
|
||||
});
|
||||
|
||||
FS.HTTP.publish(Images, function () {
|
||||
return Images.find();
|
||||
});
|
||||
|
||||
Meteor.methods({
|
||||
addTestImage: function() {
|
||||
Images.remove({});
|
||||
var url = "http://cdn.morguefile.com/imageData/public/files/b/bboomerindenial/preview/fldr_2009_04_01/file3301238617907.jpg";
|
||||
var fsFile = Images.insert(url);
|
||||
return fsFile._id;
|
||||
}
|
||||
});
|
||||
|
||||
//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)
|
||||
Loading…
Add table
Add a link
Reference in a new issue