(mostly because it's annoying to work 'alone', if you are interested to take ownership of it, use it, develop on it, let me know)
ManifMaker is a single page web app aimed to plan and organize events where volunteers take a great part.
In a few words, organizers create tasks describing the job to be done, add time slot defining when the task has to be done and specify people needs to explicit how many and what kind of volunteers are needed to perform the task.
Volunteers register on the app to add a few availabilities and skills to detail when they want to work and what they can do.
Once the tasks and their needs are validated, organizers assign volunteers to tasks according to :
You can find a live demo here.
This TOC has been generated using
docker run --rm -v $(pwd)/:/root/ meedan/base gh-md-toc /root/README.md
The project relies on Meteor, a full stack single page app framework with real time capabilities.
git clone https://github.com/assomaker/manifmaker.git
cd PATH_TO_REPO/app
meteor
Windows User : you need to install Git if you don't already have it
Windows User : if Meteor is not a known command, add meteor to your path. Meteor binary can be found here C:\Users\YOU\AppData\Local.meteor
Dev tools that are already installed and available to be used when implementing cool features.
JSDoc is used to generate doc from annotations on code. The generated doc is available as Markdown in the repo /doc/markdown or as HTML in the stagging machine (NOT DEPLOYED ANYMORE).
The HTML doc is automatically build and deployed (DISABLED), see Continuous Deployment section. The Markdown doc has to be build and commit/push when it's relevant.
npm install jsdoc -g
npm run doc:html
If you don't have npm globally installed, you can use the one provided by meteor. Add 'meteor' before npm command to do so.
Open doc/html/index.html in a browser.
npm install jsdoc-to-markdown --save-dev
npm run doc:md
If you don't have npm globally installed, you can use the one provided by meteor. Add 'meteor' before npm command to do so.
Generated in /doc/markdown
<i class="mdi mdi-home"></i>
Icon definition can be found here : https://materialdesignicons.com/.
Some useful classes implemented in css :
.clickable cursor is a hand over this element ;
.hide-on-small-devices the element is only displayed on large devices
.hide-on-small-devices the element is only displayed on large devices
User friendly alerting use s-alert. You basically only need
sAlert.error('Your message');
sAlert.warning('Your message');
sAlert.info('Your message');
sAlert.success('Your message');
Alert box will be displayed 2.5 seconds, if 'Your message' if too long to be read in 2.5 seconds you can override it with (in ms) :
sAlert.error('Your message',{ timeout : 60000 });
BootBox has to be used to display a confirmation box or a prompt box.
bootbox.confirm("Are you sure ?", function(result){
if(result){
//user was sure
}
});
Do not use alert or custom dialog features as S-Alert is the preferred way.
A powerfull custom selector is available. It is largely inspired by Github selector and provides following features :
You can refer to the auto-generated doc select-component.md and the live demo (need to be logged in): /demo-select, or localhost:3000/demo-select.
A Reference collection is used when the user as a choice between a set of editable values. Typically, you will need a reference collection with form field using a Custom Select (Teams, Places or Group Roles can be dynamically edited while being available in a select). All reference collection are editable in a page (/conf-maker) linked to a role CONFMAKER.
Each reference collection provides a set of features :
The good news is that a few configuration is needed to get all those features out of the box.
Add the schema to /both/collection/schema/CollectionReference.js. It will create Schema and Mongo Collection and generate every needed routes)
See Schemas.references.Teams for a minimal collection reference example.
Let's say you want a collection (eg: Equipment) to reference another reference collection (eg: EquipmentCategory) to allow a link between the two (eg : Equipement refers to a EquipmentCategory)
CollectionName_Id : eg EquipmentCategories_Id
It will display the "name" field of the reference collection (eg: EquipmentCategory) in the insert/uptate form (eg:Equipment).
Basic references field with custom verification that the _id actually exists and autoform to generate the dropdown
# eg: EquipmentCategory contains a list of Equipment
EquipmentCategories_Id: {
type: SimpleSchema.RegEx.Id,
label: "Equipment Category",
custom: function () {
if (!EquipmentCategories.findOne(this.value))
return "unknownId";
return 1
},
autoform: {
afFieldInput: {
options: Schemas.helpers.allEquipmentCategoriesOptions
}
},
},
Please note that you need to add the following fields to have the "update" button working (sorry...)
baseUrl: {
type: String,
label: "Team base URL",
defaultValue: "team"
}
Please note that you need to add the following fields to have the "remove" button working (sorry...)
type: {
type: String,
label: "Teams type",
defaultValue: "Teams"
},
Add the newly created Mongo Collection to the AllCollections array in /client/routes/config/route-collection-references.js.
Add your specific template in /client/templates/references/ (just copy/paste and update the existing templates to your needs. Be careful with singular and plural to have everything correctly generated)
and add your new Collection to publish/subscribe policy
You should follow the current populate/clean policy
If everything went well :
Keep on eye on the consoles (server and client) and fix all errors.
Carefully check singular and plural and that your naming is similar to the existing (use Team as an example).
In dev mode (when app is run using meteor run), you can inject data by using the URL :
Details regarding authentication data can be found here :
A super admin user (superadmin/superadmin in dev mode) is created at startup no matter what. This user has all existing roles, it can't be updated or removed and doesn't have to be used for anything else that injecting data (dev mode, stagging) or create user with roles (production, at least one admin user with ROLE role to add roles to other users).
Access Right Security uses alanning:roles.
Thoses following verifications are done (and every new features should uses all these verifications) :
SecurityServiceClient.grantAccessToPage(RolesEnum.TASKREAD);
SecurityServiceServer.grantAccessToCollection(this.userId,RolesEnum.USERREAD,"users")
SecurityServiceServer.grantAccessToItem(userId, RolesEnum.USERWRITE, doc, 'user udpate');
Following is a long pamphlet about data, you don't normally want to read it
Data validation is done inside the schemas (both/collection/model). Simple Schema provides common validations like date, string, int. Other validation can be done in custom methods with custom code to validate dates overlapping, fields updates according to validation state...
If a single operation/action needs several atomic database update, all validation has to be done explicitly BEFORE updating the data. If the validation is done one times (in the operation/action), no need to put it in the schema, just do it along with the action/operation. If not and you have to put the validation in the schema and use pre validation from Simple Schema BEFORE updating any data. This way you ensure that the data updates will not fail.
That is because of one thing : Meteor+MongoDB is not transactional. Indeed, if one of the database update fails because of a custom control that throws an error, the previous database updates will not be reverted and the following could occurs if you don't use a callback or manage the update return by yourself.
Lets take two example : update an assignment term and perform an assignment :
Updating a assignment term uses ONE atomic database. An update on AssignmentTerms collection. The controls can be performed on the related schema without any side effects. If the requirements are not satisfied, the update will just fail and the error can be displayed to the user.
Performing an assignment used THREE atomic database update (actually SIX but let's simplify the example) :
(Let's cut it short : it could have been prevented by another data design where the assignment information is only store in one place (Assignments) instead of having the data copied in the Task and the User.)
From here, two philosophies to take into account whether you agree or not to :
"it's easier to ask forgiveness than it is to get permission" which can be explained by "try to do it and if it fails, repair it"
When assigning, either you firstly check everything (user is available, task is ready, people need specs matches the user) and if it's ok, you perform the assignment or you perform the assignment and revert it if something failed. When choosing what to do you have to keep in mind that Meteor is real time, if you update something on the DB, it will be broadcasted to everyone subscribed. If you update something and revert it right away, you will unefficiently use DDP, the clients will compute the data and probably display something for a short amount of time before the sytem reverts the changes. It can lead the GUI to flickr. That is why it is probably better to check everything BEFORE database operations if you need more than one database update to perform one operation/action).
Javascript Web Token can be used to sign Json payload into a string token that can be sent/shared and can only be read by the app (like to create a one time login token)
A simple Docker image with wkhtmltopdf installed
Cmd t tests
OUTPUTL_FOLDER=/Users/remi/sandbox;
PDF_FILE=output.pdf;
IN=192.168.192.4:3000/jwt/eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0YXJnZXQiOiJodHRwOi8vbG9jYWxob3N0OjMwMDAvdXNlci9uRHd3UnlQYnVDWlo4UTZwQS9leHBvcnQiLCJ0eXBlIjoidXJsIiwiaWF0IjoxNDg5NTI4NTgzfQ.DF98Qq7jqWK_qYcPL5JU0wrY97soU2JRb22S2_b-q7M
docker run --rm -v $OUTPUTL_FOLDER:/root/out/ --env IN=$IN --env OUT=/root/out/$PDF_FILE assomaker/wkhtmltopdf
Export Node PDF docker pull assomaker/wkhtmltopdf image at startup
Cmd to test (from repo root folder)
OUTPUTDIR=$(pwd)/exported-pdf
# build node app
docker build -t assomaker/export_pdf production/export-pdf-node
# use node app in dev mode (with code in shared volume)
docker rm -f nodeexport; docker run --env OUTPUTDIR=$OUTPUTDIR --network host -v /var/run/docker.sock:/var/run/docker.sock --name nodeexport -p 3030:3030 -d -v $(pwd)/production/export-pdf-node:/root --entrypoint="" assomaker/export_pdf tail -f /dev/null; docker exec -ti nodeexport sh
$ cd /root/app/; npm install; node app.js
# use node app in normal mode (code in image)
docker rm -f nodeexport; docker run --env OUTPUTDIR=$OUTPUTDIR -v /var/run/docker.sock:/var/run/docker.sock --name nodeexport -p 3030:3030 -d assomaker/export_pdf; docker logs -f nodeexport
# Nginx to serve file
docker rm -fv nginx; docker run --name nginx -p 8080:80 -d -v $OUTPUTDIR:/usr/share/nginx/html/pdf nginx
An endpoint is available to retrieve data as Json: api/:resource/:action
.
Available resources and actions can be found at ApiResourceActionService.js#L4
jsonExport: true
to enable simple type export (string, numbers...)jsonExportCustom: function (value) { /*do something with value*/; return value}
to customise the data format. You can query the db to resolve _ids for example.
return {
newValue: "newValue",
newKey: "newkeyinjson"
}
note that newKey can use the dot notation
For Dev/Preprod:
Whatever data will be added only once, even if ManifMaker app is restarted.
Whatever data will wiped out for each anifMaker app restart.
Which of the defined InjectDataServerService to use to inject various data.
Inject Roles define in Roles enum, add a superadmin group roles (not updatable) and a superadmin user (not updatable).
Superadmin user has "superadmin" password in Development and a random one in Production. Superadmin password can be found in the app log when starting.
For Production:
Skip injected init access right (superadmin user and roles) even if it has never been injected. Either the roles and superadmin users are already there or you will seed your database differently (not recommended).
Force isDevelopment mode to allow wiping out and injected data. It is only valid at startup, 'isProduction' remains true for the app lifecycle. Value should be 'truetrue', just because we don't want to easily allow deleting data in production.
Value should be 'iknowwhatiamdoing' to enable usage of IT_IS_NOT_PRODUCTION_IT_IS_OK_TO_DELETE_DATA. The usage for both env is not being explained in the logs.
If MAILGUN_PASSWORD, MAIL_URL will be set
Key use by JWT to sign a Json payload. If JWT_PUBLIC_KEY is not set, a random secret will be generated and used to both sign and verify (way less secure)
Key use by JWT to verify a Json payload. If JWT_PRIVATE_KEY is not set, a random secret will be generated (way less secure)
Where the Node app can be reached. the ManifMaker app is POSTing to this endpoint when PDF need to be generated.
Where the Nginx serving the PDF can be reached. Currently no authentication whatsoever.
Where the ManifMaker can be reached. It used by assomaker/wkhtmltopdf to load the HTML page that needs to be exported as PDF
See https://console.developers.google.com/apis/credentials
https://developers.facebook.com/docs/apps/register/
https://docs.docker.com/engine/installation
https://docs.docker.com/compose/install/
clone repo and use Compose
git clone https://github.com/assomaker/manifmaker.git
cd manifmaker/production
docker-compose up -d
docker-compose --file docker-compose-preproduction.yml up -d
# or for production
docker-compose up -d
__ManifMaker will fail to start because it can't connect to mongo. You currently need to had by hand the ManifMaker mongo user. A special Docker production_mongodb image will be used in a near future__
chmod 777 ~/manifmaker_images
docker cp create_manifmaker_mongo_user.js production_mongodb:/root/create_manifmaker_mongo_user.js
docker cp create_backup_mongo_user.js production_mongodb:/root/create_backup_mongo_user.js
docker exec production_mongodb mongo localhost:27017/admin /root/create_backup_mongo_user.js
docker exec production_mongodb mongo localhost:27017/manifmaker /root/create_manifmaker_mongo_user.js
docker-compose up -d manifmaker
__777 on ~/manifmaker_images seems to be required by Fs Collection to store image, it didn't even work with 666. It can be a security flaw as we are giving exec access to a volume shared in a Docker__
"version": "0.3.0",
You need to have matching version number between docker-compose-preproduction.yml and package.json to deploy the version you just built.
Current update policy provokes a service interruption as there is only one ManifMaker node. Following steps should get easier one day.
Make sure the version you are updating to is available on the Docker hub.
update REPO/production/docker-compose.yml (or docker-compose-preproduction.yml) base image of manifmaker service
manifmaker:
image: 'assomaker/manifmaker:0-10-0'
A Mongo level backup is run everyday at midnight, it backups all /manifmaker database.
See the list of backups, you can run:
docker exec mongodb_backup ls /backup
To restore database from a certain backup, simply run:
docker exec mongodb_backup /restore.sh /backup/2015.08.06.171901
It will delete everything (--drop) and restore whole /manifmaker database.
docker exec mongodb_backup /backup.sh
Travis CI is used to achieve Continuous Deployment.
When a push occurs on branch deploy :
When a push occurs on branch production :
We are using the Github issues enhanced with Zenhub product which I recommend to install.
Our specs and manuals tests are written in a GDoc, ask me if you want access to it.