diff --git a/index.js b/index.js index 1e3d961..00d5d2a 100644 --- a/index.js +++ b/index.js @@ -103,7 +103,8 @@ relations.define = function (name, structure) { relations.stores = { memory: require('./stores/memory'), mysql: require('./stores/mysql'), - redis: require('./stores/redis') + redis: require('./stores/redis'), + mongoose: require('./stores/mongoose_backend') }; relations.use = function (store, options) { diff --git a/package.json b/package.json index 7965caa..01058f0 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "index.js", "dependencies": { "eventflow": "0.0.6", - "async": "~0.1.22" + "async": "~0.1.22", + "mongoose": "~3.8.1" }, "devDependencies": { "mocha": "*", diff --git a/parser.js b/parser.js index ba7b0a3..18b8030 100644 --- a/parser.js +++ b/parser.js @@ -47,6 +47,7 @@ module.exports = (function(){ "VerbSubjectRequest": parse_VerbSubjectRequest, "RoleSubjectRequest": parse_RoleSubjectRequest, "ObjectVerbRequest": parse_ObjectVerbRequest, + "ObjectRoleRequest": parse_ObjectRoleRequest, "Token": parse_Token, "NamedToken": parse_NamedToken, "UnnamedToken": parse_UnnamedToken, @@ -121,21 +122,24 @@ module.exports = (function(){ pos0 = pos; result0 = parse_ObjectVerbRequest(); if (result0 === null) { - result0 = parse_RoleSubjectRequest(); + result0 = parse_ObjectRoleRequest(); if (result0 === null) { - result0 = parse_VerbSubjectRequest(); + result0 = parse_RoleSubjectRequest(); if (result0 === null) { - result0 = parse_RoleQuestion(); + result0 = parse_VerbSubjectRequest(); if (result0 === null) { - result0 = parse_RoleRequest(); + result0 = parse_RoleQuestion(); if (result0 === null) { - result0 = parse_Declaration(); + result0 = parse_RoleRequest(); if (result0 === null) { - result0 = parse_Revocation(); + result0 = parse_Declaration(); if (result0 === null) { - result0 = parse_VerbRequest(); + result0 = parse_Revocation(); if (result0 === null) { - result0 = parse_VerbQuestion(); + result0 = parse_VerbRequest(); + if (result0 === null) { + result0 = parse_VerbQuestion(); + } } } } @@ -1091,6 +1095,108 @@ module.exports = (function(){ return result0; } + function parse_ObjectRoleRequest() { + var result0, result1, result2, result3, result4, result5, result6; + var pos0, pos1; + + reportFailures++; + pos0 = pos; + pos1 = pos; + if (input.substr(pos, 16).toLowerCase() === "what roles does ") { + result0 = input.substr(pos, 16); + pos += 16; + } else { + result0 = null; + if (reportFailures === 0) { + matchFailed("\"what roles does \""); + } + } + if (result0 !== null) { + result1 = parse_Token(); + if (result1 !== null) { + if (input.substr(pos, 5).toLowerCase() === " have") { + result2 = input.substr(pos, 5); + pos += 5; + } else { + result2 = null; + if (reportFailures === 0) { + matchFailed("\" have\""); + } + } + if (result2 !== null) { + result3 = parse_Preposition(); + if (result3 !== null) { + if (input.charCodeAt(pos) === 32) { + result4 = " "; + pos++; + } else { + result4 = null; + if (reportFailures === 0) { + matchFailed("\" \""); + } + } + if (result4 !== null) { + result5 = parse_Token(); + if (result5 !== null) { + if (input.charCodeAt(pos) === 63) { + result6 = "?"; + pos++; + } else { + result6 = null; + if (reportFailures === 0) { + matchFailed("\"?\""); + } + } + result6 = result6 !== null ? result6 : ""; + if (result6 !== null) { + result0 = [result0, result1, result2, result3, result4, result5, result6]; + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + } else { + result0 = null; + pos = pos1; + } + if (result0 !== null) { + result0 = (function(offset, subject, object) { + return { + type: "object-role-request", + subject: subject, + object: object + } + })(pos0, result0[1], result0[5]); + } + if (result0 === null) { + pos = pos0; + } + reportFailures--; + if (reportFailures === 0 && result0 === null) { + matchFailed("object role request"); + } + return result0; + } + function parse_Token() { var result0; diff --git a/parser.pegjs b/parser.pegjs index b81dce7..8c727fc 100644 --- a/parser.pegjs +++ b/parser.pegjs @@ -5,6 +5,7 @@ start = ret:( ObjectVerbRequest + / ObjectRoleRequest / RoleSubjectRequest / VerbSubjectRequest / RoleQuestion @@ -103,6 +104,15 @@ ObjectVerbRequest "object verb request" } } +ObjectRoleRequest "object role request" + = "what roles does "i subject:Token " have"i Preposition " " object:Token "?"? { + return { + type: "object-role-request", + subject: subject, + object: object + } + } + Token "token" = NamedToken / UnnamedToken / Literal diff --git a/stores/mongoose_backend.js b/stores/mongoose_backend.js new file mode 100644 index 0000000..476f64f --- /dev/null +++ b/stores/mongoose_backend.js @@ -0,0 +1,167 @@ +/** + * @author Jan Zaloudek + * @date 27.10.13 + * @time 16:38 + * @filename + */ + +var store = module.exports = require('eventflow')(); +var RelationModel = require('./mongoose_model.js'); + +store.on('init', function (options, cb) { + cb(); +}); + +store.on('declaration', function (cmd, cb) { + var relation = new RelationModel({ + context: cmd.ctx.name, + subject: cmd.subject, + role: cmd.role, + object: cmd.object || "" + }); + + relation.save(function(err) { + if(err && err.code === 11000) return cb(); + + return cb(err); + }); +}); + +store.on('revocation', function (cmd, cb) { + RelationModel.findOneAndRemove({ + context: cmd.ctx.name, + subject: cmd.subject, + role: cmd.role, + object: cmd.object + }, cb); +}); + +store.on('verb-question', function (cmd, cb) { + RelationModel.findOne({ + context: cmd.ctx.name, + subject: cmd.subject, + role: { $in: cmd.ctx.verbs[cmd.verb] }, + $or: [{ + object: cmd.object || "", + }, + { + object: '' + }] + }, function(err, relation) { + if(err) return cb(err); + + if(relation) { + return cb(null, true); + } + + cb(null, false); + }); +}); + +store.on('role-question', function (cmd, cb) { + RelationModel.findOne({ + context: cmd.ctx.name, + subject: cmd.subject, + role: cmd.role, + $or: [{ + object: cmd.object || "", + }, + { + object: '' + }] + }, function(err, relation) { + if(err) { + return cb(err); + } + + if(relation) { + return cb(null, true); + } + + cb(null, false); + }); +}); + +store.on('verb-request', function (cmd, cb) { + RelationModel.find({ + context: cmd.ctx.name, + subject: cmd.subject, + role: { $in: cmd.ctx.verbs[cmd.verb] }, + object: { $ne: '' } + }, 'object', function(err, relations) { + if(err) return cb(err); + + cb(null, relations.map(function(relation) { return relation.object; })); + }); +}); + +store.on('role-request', function (cmd, cb) { + RelationModel.find({ + context: cmd.ctx.name, + subject: cmd.subject, + role: cmd.role, + object: { $ne: '' } + }, 'object', function(err, relations) { + if(err) return cb(err); + + cb(null, relations.map(function(relation) { return relation.object; })); + }); +}); + +store.on('verb-subject-request', function (cmd, cb) { + RelationModel.find({ + context: cmd.ctx.name, + object: cmd.object, + role: { $in: cmd.ctx.verbs[cmd.verb] } + }, 'subject', function(err, relations) { + if(err) return cb(err); + + cb(null, relations.map(function(relation) { return relation.subject; })); + }); +}); + +store.on('role-subject-request', function (cmd, cb) { + RelationModel.find({ + context: cmd.ctx.name, + object: cmd.object, + role: cmd.role + }, 'subject', function(err, relations) { + if(err) return cb(err); + + cb(null, relations.map(function(relation) { return relation.subject; })); + }); +}); + +store.on('object-verb-request', function (cmd, cb) { + RelationModel.find({ + context: cmd.ctx.name, + object: cmd.object, + subject: cmd.subject + }, 'role', function(err, relations) { + if(err) return cb(err); + + cb(null, relations.reduce(function (verbs, row) { + return verbs.concat( cmd.ctx.roles[row.role] || [] ); + }, [])); + }); +}); + +store.on('object-role-request', function(cmd, cb) { + RelationModel.find({ + context: cmd.ctx.name, + object: cmd.object, + subject: cmd.subject + }, 'role', function(err, relations) { + if(err) return cb(err); + + var roles = relations.map(function (row) { + return ( row.role ); + }); + + cb(null, roles); + }); +}); + +store.on('reset', function (cb) { + RelationModel.remove({}, cb); +}); diff --git a/stores/mongoose_model.js b/stores/mongoose_model.js new file mode 100644 index 0000000..89c1fb5 --- /dev/null +++ b/stores/mongoose_model.js @@ -0,0 +1,31 @@ +/** + * @author Jan Zaloudek + * @date 28.10.13 + * @time 0:25 + * @filename + */ + +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; + + +//client.query("CREATE TABLE IF NOT EXISTS `relations` (" +// + "`context` VARCHAR(255) NOT NULL," +// + "`subject` VARCHAR(255) NOT NULL," +// + "`role` VARCHAR(255) NOT NULL," +// + "`object` VARCHAR(255) NOT NULL," +// + "PRIMARY KEY (`context`, `subject`, `role`, `object`)," +// + "INDEX object (`context`, `role`, `object`)" +// + ") ENGINE=InnoDB", cb); + +var RelationSchema = new Schema({ + context: String, + subject: String, + role: String, + object: String +}); + +RelationSchema.index({ context: 1, subject: 1, role: 1, object: 1 }, { unique: true }); +RelationSchema.index({ context: 1, role: 1, object: 1 }); + +module.exports = mongoose.model('Relation', RelationSchema); \ No newline at end of file diff --git a/test/basic.js b/test/basic.js index 3e33a08..11c628b 100644 --- a/test/basic.js +++ b/test/basic.js @@ -10,4 +10,10 @@ describe('mysql store', function () { describe('redis store', function () { var redis = require('redis'); doBasicTest('redis', {client: redis.createClient(), prefix: 'relations-test:test-prefix'}); +}); + +describe('mongoose store', function () { + var mongoose = require('mongoose'); + mongoose.connect('mongodb://127.0.0.1/relations_test'); + doBasicTest('mongoose'); }); \ No newline at end of file diff --git a/test/common.js b/test/common.js index d1dd0cb..141a7f2 100644 --- a/test/common.js +++ b/test/common.js @@ -28,6 +28,7 @@ doBasicTest = function (store, options) { relations.repos(':user is the "owner" of :repo', {user: brian, repo: views}); relations.repos('%s is a watcher', brian); relations.repos('%s is a watcher', sagar); + }); after(relations.tearDown); @@ -112,6 +113,14 @@ doBasicTest = function (store, options) { }); }); + it('what roles does brian have in views', function (done) { + relations.repos('what roles does %s have in %s', brian, views, function (err, list) { + assert.ifError(err); + assert.deepEqual(list.sort(), [ 'owner' ]); + done(); + }); + }); + it('who is the owner of views?', function (done) { relations.repos('who is the owner of %s?', views, function (err, list) { assert.ifError(err);