Super flexible and lightweight factory for creating publications using custom validators.
Principle
First and foremost you want your publications to be created from config objects (similar to mdg:validated-method
).
But of course, you want strong security for your publications but also flexibility to choose, which technologies you use to enforce this security.
Therefore, you write custom validators that apply for all publications, that are created using a factory instance. You basically abstract your validations away, so your publications will only need configuration.
At the same time you want to focus your publication logic on the data layer, no checks, validations etc.
Therefore, you add a run
function to your config, that only cares about which data to query from which collection
and how to transform if, finally returning a cursor.
That's it - separation of concerns together with a flexible dependency injection.
Installation
1meteor add jkuester:publication-factory
Changelog
2.0.0
- Major change using generic validators
- No binding to external packages
Documentation
The factory is a configurable class, so you can create different factories using different configurations.
Minimal example
You can for example leave the config empty to create a minimal factory:
1const factory = new PublicationFactory() 2factory.create({ 3 name: 'allMyDocuments', 4 run: function({ limit }) { 5 const createdBy = this.userId 6 return MyCollection.find({}, { createdBy, limit }) 7 } 8})
In this base configuration there is no validation at all. If you want to add custom validations, you need to define custom validators and add them to the factory configuration.
Writing Validators
A validator is a function that receives the create options and returns a validation function based on these options:
1({}) => (...*) => undefined|throw
The validation function should throw an Error if validation fails, otherwise return undefined / no return.
The following example defines a validator for an arguments schema and for user logged-in status:
1const validateArgs = function({ schema }) { 2 if (!schema) throw new Error('expected schema') // makes the field required 3 const schema = new SimpleSchema(schema) 4 return function validate (...args) { 5 schema.validate(...args) 6 } 7} 8 9const validateUser = function({ name, isPublic }) { 10 if (isPublic) return 11 return function validate () { 12 // this-context will be bound to the publication function 13 // so we can access the user as in any normal publication 14 const { userId } = this 15 if (!userId || !Meteor.users.findOne(userId)) { 16 throw new Meteor.Error(403, 'publication.permissionDenied', name) 17 } 18 } 19} 20 21const validators = [ validateArgs, validateUser ] 22const factory = new PublicationFactory({ validators }) 23factory.create({ 24 name: 'allMyDocuments', 25 isPublic: false, 26 schema: { limit: Number }, 27 run: function({ limit }) { 28 const createdBy = this.userId 29 return MyCollection.find({}, { createdBy, limit }) 30 } 31})
Full Example
The following example creates a new publication and uses a custom validation for a user to have certain roles.
This involves a third party package (alanning:roles
) and you are not bound to use this specific package,
since the validators are up to you.
First we create the validators:
1const validateArgs = function() { 2 return function validate (...args) { 3 if (args.length > 0) throw new Error('expected no args') 4 } 5} 6 7const validateRoles = function({ roles, group }) { 8 if (!roles) return 9 10 return function validate () { 11 const { userId } = this 12 if (!userId || !Roles.userIsInRoles(userId)) { 13 throw new Meteor.Error(403, 'publication.permissionDenied', name) 14 } 15 } 16}
Second, we create the factory while injecting the validators:
1const validators = [validateRoles] 2const factory = new PublicationFactory({ validators })
Third, we create a specific publication using the factory:
1const Cars = new Mongo.Collection('cars'); 2const prototypeCars = factory.create({ 3 name: 'prototypeCars', 4 roles: ['camViewPrototypes'], 5 group: 'researchers', 6 run: function() { 7 return Cars.find({ isPrototype: true }) 8 } 9}) 10 11// note that we could automatically publish using 12// the `publish` flag in the config for factory.create 13Meteor.publish('prototypeCars', prototypeCars)
Testing
You can run the tests using the test script or using the following bash line:
$ METEOR_PACKAGE_DIRS=../ TEST_WATCH=1 meteor test-packages ./ --driver-package meteortesting:mocha
Contribution
Every contribution is very welcome. Feel free to raise an issue, if there is any problem with this package.
License
MIT, see license file