connect-cloudant-store
Advanced tools
Comparing version 0.0.4 to 0.1.0
@@ -68,2 +68,5 @@ /** | ||
this.disableTTLRefresh = options.disableTTLRefresh || false; | ||
this.dbViewName = options.dbViewName || 'express_expired_sessions'; | ||
this.dbDesignName = options.dbDesignName || 'expired_sessions'; | ||
this.dbRemoveExpMax = options.dbRemoveExpMax || 100; | ||
if (options.client) { | ||
@@ -231,3 +234,71 @@ this.client = options.client; | ||
CloudantStore.prototype.cleanupExpired = function() { | ||
return new Promise(function(resolve, reject) { | ||
self.initView().then(function() { | ||
self.db.view(self.dbDesignName, self.dbViewName, {limit: self.dbRemoveExpMax}) | ||
.then(function(body) { | ||
// bulk delete | ||
if (body.total_rows > 0) { | ||
var delRows = []; | ||
body.rows.forEach(function(row) { | ||
delRows.push( | ||
{ | ||
_id: row.key, | ||
_rev: row.value, | ||
_deleted: true | ||
} | ||
); | ||
}); | ||
debug('cleanupExpired - BULK delete %s', JSON.stringify(body.rows)); | ||
self.db.bulk({docs: delRows}, null) | ||
.then(function() { resolve(); }) | ||
.catch(function(err) { reject(err); }); | ||
} else { | ||
debug('cleanupExpired - Nothing to delete'); | ||
resolve(); | ||
} | ||
}).catch(function(err) { | ||
debug('cleanupExpired - ERROR reading expired sessions', JSON.stringify(err)); | ||
reject(err); | ||
}); | ||
}).catch(function(err) { | ||
debug('cleanupExpired - Failed to load/create views', JSON.stringify(err)); | ||
reject(err); | ||
}); | ||
}); | ||
}; | ||
CloudantStore.prototype.initView = function() { | ||
return new Promise(function(resolve, reject) { | ||
self.db.view(self.dbDesignName, self.dbViewName, {limit: 0}) | ||
.then(function() { resolve(); }) | ||
.catch(function(err) { | ||
if (err.statusCode === 404) { | ||
// try to create DB | ||
debug('View for expired session doesn\'t exists - Try to create'); | ||
var designDoc = {'views': {}}; | ||
designDoc['views'][self.dbViewName] = { | ||
'map': function(doc) { | ||
if (doc.session_ttl && doc.session_modified && | ||
Date.now() > (doc.session_ttl + doc.session_modified)) { | ||
// eslint-disable-next-line | ||
emit(doc._id, doc._rev); | ||
} | ||
} | ||
}; | ||
self.db.insert(designDoc, '_design/' + self.dbDesignName) | ||
.then(function() { resolve(); }) | ||
.catch(function(err) { | ||
reject(err); | ||
}); | ||
} else { | ||
reject(err); | ||
} | ||
}); | ||
}); | ||
}; | ||
return CloudantStore; | ||
}; |
{ | ||
"name": "connect-cloudant-store", | ||
"version": "0.0.4", | ||
"version": "0.1.0", | ||
"description": "Node JS express-session storage connector for IBM Cloudant", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -32,2 +32,3 @@ # connect-cloudant-store | ||
// Cloudant Session store is ready for use | ||
// you can call store.cleanupExpired() here or set it to be executed at an interval | ||
}); | ||
@@ -49,3 +50,3 @@ | ||
Standard usage for Bluemix environment/dev environment : | ||
Standard usage for Bluemix (public) environment : | ||
@@ -66,2 +67,12 @@ ```javascript | ||
### Using the session auto-clean feature | ||
The storage class has a auto-clean specific method that has to be called in your code. It could be called for example from a setIterval timer. It checks if there is already a view available for getting the expired sessions from store db, and if not, is trying to create it. There are related optional parameters to customize the name of the view/design, set a top limit for items deleted per cleanup call. | ||
```javascript | ||
store.on('connect', function() { | ||
// Cloudant Session store is ready for use | ||
setInterval(function() { store.cleanupExpired(); }, 3600 * 1000); here or set it to be executed at an interval | ||
}); | ||
``` | ||
## Store Parameters | ||
@@ -79,2 +90,5 @@ | ||
disableTTLRefresh: false, | ||
dbViewName: 'express_expired_sessions'; | ||
dbDesignName: 'expired_sessions'; | ||
dbRemoveExpMax: 100, | ||
// Cloudant() parameters used if 'client' is not provided | ||
@@ -92,3 +106,3 @@ url: undefined, | ||
### url | ||
Allows to create the Cloudant client based on the url | ||
Allows to create the Cloudant client based on the url (containing credentials) | ||
@@ -108,3 +122,3 @@ ex: | ||
Note: This will not work on *Bluemix Dedicated* because cloudant library is not searching by service name first, but instead by service type key first | ||
**Note:** This will not work on *Bluemix Dedicated* because cloudant library is not searching by service name first, but instead by service type key first | ||
and second by service name (instanceName); | ||
@@ -141,2 +155,11 @@ | ||
### dbViewName | ||
Name of the expired session view to be used for building the expired sessions list | ||
### dbDesignName | ||
Name of the couch db design name to be used for building the expired sessions list - if the design and the view is not found in the cloudant database, the first call to store.cleanupExpired() will try to create it. | ||
### dbRemoveExpMax | ||
Limits the maximum amount of sessions to be bulk deleted per each store.cleanupExpired() call. | ||
## Debugging | ||
@@ -170,3 +193,3 @@ | ||
PR code should be covered by UT | ||
PR code should have UT associated with a good coverage | ||
@@ -193,2 +216,2 @@ ``` | ||
[downloads-image]: https://img.shields.io/npm/dm/connect-cloudant-store.svg | ||
[downloads-url]: https://npmjs.org/package/connect-cloudant-store | ||
[downloads-url]: https://npmjs.org/package/connect-cloudant-store |
@@ -29,9 +29,8 @@ 'use strict'; | ||
'insert': function() {}, | ||
'destroy': function() {} | ||
'destroy': function() {}, | ||
'bulk': function() {}, | ||
'view': function() {} | ||
}; | ||
var client = new Cloudant(goodClientParams); | ||
// dbPutStub, | ||
// dbDestroyStub, | ||
// dbHeadStub; | ||
@@ -46,3 +45,2 @@ describe('TestSuite - CloudantStore', function() { | ||
clientGetDbStub = sinon.stub(client, 'use').returns(dbStub); | ||
// dbDestroyStub = sinon.stub(Cloudant.prototype, 'destroy'); | ||
}); | ||
@@ -391,2 +389,225 @@ | ||
}); | ||
describe('TestSuite - CloudantStore - Cleanup Sessions', function() { | ||
var dbViewStub, | ||
dbBulkStub, | ||
goodViewObj; | ||
beforeEach(function() { | ||
dbInfoStub = sinon.stub(dbStub, 'info'); | ||
dbInsertStub = sinon.stub(dbStub, 'insert'); | ||
dbViewStub = sinon.stub(dbStub, 'view'); | ||
dbBulkStub = sinon.stub(dbStub, 'bulk'); | ||
clientGetDbStub = sinon.stub(client, 'use').returns(dbStub); | ||
goodViewObj = { | ||
'total_rows': 1, | ||
'rows': [ | ||
{key: 'sess:sess-id-1', value: 'rev-id-1'}, | ||
{key: 'sess:sess-id-2', value: 'rev-id-2'} | ||
] | ||
}; | ||
}); | ||
afterEach(function() { | ||
clientGetDbStub.restore(); | ||
dbInfoStub.restore(); | ||
dbViewStub.restore(); | ||
dbBulkStub.restore(); | ||
dbInsertStub.restore(); | ||
}); | ||
it('Testcase - initView - existing view resolves', function(done) { | ||
dbInfoStub.returns(Promise.resolve({})); | ||
dbViewStub.returns(Promise.resolve({})); | ||
var store = new CloudantStore({ | ||
client: client | ||
}); | ||
store.initView(); | ||
setTimeout(function() { | ||
expect(dbViewStub.called).to.be.true; | ||
expect(dbInsertStub.called).to.be.false; | ||
done(); | ||
}, 0); | ||
}); | ||
it('Testcase - initView - no view non 404 - fail', function(done) { | ||
dbInfoStub.returns(Promise.resolve({})); | ||
var error = {statusCode: 409, message: 'Some message'}; | ||
dbViewStub.returns(Promise.reject(error)); | ||
var store = new CloudantStore({ | ||
client: client | ||
}); | ||
store.initView() | ||
.then(function() { | ||
expect(false).to.be.true; | ||
done(); | ||
}) | ||
.catch(function(err) { | ||
expect(dbViewStub.called).to.be.true; | ||
expect(dbInsertStub.called).to.be.false; | ||
expect(err).to.be.ok; | ||
expect(err).to.deep.equal(error); | ||
done(); | ||
}); | ||
}); | ||
it('Testcase - initView - no view 404 - success to create', function(done) { | ||
dbInfoStub.returns(Promise.resolve({})); | ||
var error = {statusCode: 404, message: 'not found'}; | ||
dbViewStub.returns(Promise.reject(error)); | ||
dbInsertStub.returns(Promise.resolve()); | ||
var store = new CloudantStore({ | ||
client: client | ||
}); | ||
store.initView() | ||
.then(function() { | ||
expect(dbViewStub.called).to.be.true; | ||
expect(dbInsertStub.called).to.be.true; | ||
done(); | ||
}) | ||
.catch(function() { | ||
expect(false).to.be.true; | ||
done(); | ||
}); | ||
}); | ||
it('Testcase - initView - no view 404 - failed to create', function(done) { | ||
dbInfoStub.returns(Promise.resolve({})); | ||
var error = {statusCode: 404, message: 'not found'}; | ||
var insertErr = {statusCode: 500, message: 'generic error'}; | ||
dbViewStub.returns(Promise.reject(error)); | ||
dbInsertStub.returns(Promise.reject(insertErr)); | ||
var store = new CloudantStore({ | ||
client: client | ||
}); | ||
store.initView() | ||
.then(function() { | ||
expect(false).to.be.true; | ||
done(); | ||
}) | ||
.catch(function(err) { | ||
expect(dbViewStub.called).to.be.true; | ||
expect(dbInsertStub.called).to.be.true; | ||
expect(err).to.deep.equal(insertErr); | ||
done(); | ||
}); | ||
}); | ||
it('Testcase - cleanupExpired - initView failure', function(done) { | ||
dbInfoStub.returns(Promise.resolve({})); | ||
var error = {statusCode: 500, message: 'generic message'}; | ||
var store = new CloudantStore({ | ||
client: client | ||
}); | ||
sinon.stub(store, 'initView').returns(Promise.reject(error)); | ||
store.cleanupExpired() | ||
.then(function() { | ||
expect(false).to.be.true; | ||
done(); | ||
}) | ||
.catch(function(err) { | ||
expect(err).to.deep.equal(error); | ||
done(); | ||
}); | ||
}); | ||
it('Testcase - cleanupExpired - db.view failure', function(done) { | ||
dbInfoStub.returns(Promise.resolve({})); | ||
var error = {statusCode: 429, message: 'generic 429 message'}; | ||
dbViewStub.returns(Promise.reject(error)); | ||
var store = new CloudantStore({ | ||
client: client | ||
}); | ||
sinon.stub(store, 'initView').returns(Promise.resolve()); | ||
store.cleanupExpired() | ||
.then(function() { | ||
expect(false).to.be.true; | ||
done(); | ||
}) | ||
.catch(function(err) { | ||
expect(err).to.deep.equal(error); | ||
done(); | ||
}); | ||
}); | ||
it('Testcase - cleanupExpired - nothing to delete', function(done) { | ||
dbInfoStub.returns(Promise.resolve({})); | ||
dbViewStub.returns(Promise.resolve({'total_rows': 0, 'rows': []})); | ||
var store = new CloudantStore({ | ||
client: client | ||
}); | ||
sinon.stub(store, 'initView').returns(Promise.resolve()); | ||
store.cleanupExpired() | ||
.then(function() { | ||
expect(dbViewStub.called).to.be.true; | ||
done(); | ||
}) | ||
.catch(function() { | ||
expect(false).to.be.true; | ||
done(); | ||
}); | ||
}); | ||
it('Testcase - cleanupExpired - expired rows - delete fails', function(done) { | ||
var error = {statusCode: 500, message: 'generic 500 message'}; | ||
dbInfoStub.returns(Promise.resolve({})); | ||
dbViewStub.returns(Promise.resolve(goodViewObj)); | ||
dbBulkStub.returns(Promise.reject(error)); | ||
var store = new CloudantStore({ | ||
client: client | ||
}); | ||
sinon.stub(store, 'initView').returns(Promise.resolve()); | ||
store.cleanupExpired() | ||
.then(function() { | ||
expect(false).to.be.true; | ||
done(); | ||
}) | ||
.catch(function(err) { | ||
expect(err).to.deep.equal(error); | ||
expect(dbViewStub.called).to.be.true; | ||
expect(dbBulkStub.calledWith({ | ||
docs: [ | ||
{_id: 'sess:sess-id-1', _rev: 'rev-id-1', _deleted: true}, | ||
{_id: 'sess:sess-id-2', _rev: 'rev-id-2', _deleted: true} | ||
] | ||
})).to.be.true; | ||
done(); | ||
}); | ||
}); | ||
it('Testcase - cleanupExpired - expired rows - success delete', function(done) { | ||
dbInfoStub.returns(Promise.resolve({})); | ||
dbViewStub.returns(Promise.resolve(goodViewObj)); | ||
dbBulkStub.returns(Promise.resolve()); | ||
var store = new CloudantStore({ | ||
client: client | ||
}); | ||
sinon.stub(store, 'initView').returns(Promise.resolve()); | ||
store.cleanupExpired() | ||
.then(function() { | ||
expect(dbViewStub.called).to.be.true; | ||
expect(dbBulkStub.calledWith({ | ||
docs: [ | ||
{_id: 'sess:sess-id-1', _rev: 'rev-id-1', _deleted: true}, | ||
{_id: 'sess:sess-id-2', _rev: 'rev-id-2', _deleted: true} | ||
] | ||
})).to.be.true; | ||
done(); | ||
}) | ||
.catch(function() { | ||
expect(false).to.be.true; | ||
done(); | ||
}); | ||
}); | ||
}); | ||
}); |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
46690
917
209