- To @larsdecher , for letting me know about https://github.com/bures/mjml4-in-browser :
- Indeed as I explain below, I have worked on projects, in which managing email campaigns, is a feature. And it happens to be the case in many of the projects I work on, starting with every time I commit to setup a devops infrastruture and team.
- So I'am still interested in find out about new tools that process
MJML
. - What I wanna poin out here, is that htere might be a new approach to emails campaigns, considering those as involving content management. S we're speaking about a CMS to manage emails templates, right? And what is up today in the CMS world ? Headless CMS. Static sites generators turn into Static emails generators. And frol there we can hink of multi channel Headless CMS, that is, a Headless CMS able to manage multiple media, not only web site, but emails, facebook posts, tweets, @media.com articles, etc;... ALl relying on a set of static media generators, one generator for each media
Omega
, is an evolution towards Angular 8, of the well known https://grapesjs.com.
It adds it a server-side, a distributed server-side, and makes it collaboration ready.
The whole IS distributed, as you'll see, and out-of-the box, just because git
is, distributed.
Standing on the shoulders of giants.
As I was working on a software that had to send e-mails, based on beautiful templates, I learned about MJML
, a markup language designed and standardized by MailJet
, in order to webdesign email templates, for mailing campaigns.
I then found out about a wysiwyg MJML
editor, which happens to be a fork of GrapesJS
.
That MJML
editor, fork of GrapesJS, GrapesJS
allow non-technical users (that barely know HTML
, or a bit of web design, typically), to edit new email templates for their mailing campaigns, all on their own.
Which is exactly what I needed.
So I began working with this MJML
editor, fork of GrapesJS :
- Indeed, my first need was to find out how (and where) my users would persist each new email templates (for a given mailing campaign)
- So digging into the
MJML
wysiwyg editor's behind the scene, I really quickly realized that its docuementation, was mainly reduced to that of its elder, theGrapesJS
HTML
wysiwyg editor 's documentation. Now working on theGrapesJS
HTML
wysiwyg editor 's documentation, I found out :- that both GrapesJS 's
HTML
andMJML
wysiwyg editors were using a Javascript plugin mechanisms, that allows any developer to implement his own persistence module. - That this persistence module allows the developer to define how and where the editor's content is to be persited.
- That a couple of Persistence Module are provided by the GrapesJS project team, like modules allowing to persist your edited template to firebase's firestore saas offer, cf. https://github.com/artf/grapesjs-firestore. That storage service, behind the scene is a no-sql document-oriented database. Well you can think of it as if it was
MongoDB
(And now I'm thinkng about Richard Stallman...).
- that both GrapesJS 's
So starting from there I had one good idea, plus two goods reasons, to fork GrapesJS
, and launch a new project :
- [The good idea] Make GrapesJS Collaboration-Ready (bring in a Git-based default Persistence Module) : Well I was first astonsihed to find out that there are no Git oriented persistence Module, So here I go with a good idea, people being able to collaborate / validate on a given new
MJML
/HTML
template ! (MJML as Code
/HTML as Code
, or wasn't it code in the first place...?). I immediately realised that bringing in a new persistence module based on agit
storage, not only makesGrapeJS
Collaboration-Ready, but it makes it distributed. :- The
GrapesJS
initial Designer, still today, has a side company proposing SAAS hostedGrapesJS
offers, where persisting and collaborating on files by teams is made easy (or so I hope for the customers of that company). - Using the Git Persistence Module, each collaborator uses his own server (the
Omega
server), most of the time a docker container on his own machine, and the git repos to persist and share with collaborators. No central Server needed here
- The
- [good reason no. 1] Bring in Dependency Injection it's a pure client app : no server there. Plus it's implemented in pure old fashioned JavaScript. So here there's an opportunity turning that into a pure
Angular 8 PWA
(Progressive Web App). If only that, for the sake of dependency injection (Thankfully,GrapesJS
initial designer code relatively neat,but still, it's real old style JavaScript) - [good reason no. 2] Bring in a TDD Build Process since it's pure JavaScript, it's like there are zero build steps, and zero build process at all, so about the test stack, ouch.. Here Another Reason to bring webpack and Karma / Mocha / Jest / Jasmine
So :
-
Omega
will beGrapesJS
re-implemented inAngular 8 / TypeScript
, plus a TypeScript server side :- defining a clean build process, pipeline-ready, for both the client, and the server.
- using dependency injection, especially to implement the Persistence Module plugin mechanism (with an
npm install [email protected]
, and a configuration for the build)
-
When a user editing an
HTML
orMJML
template withOmega
:- clicks the
save
button, a dialog box prompts the user to fill in commit message, and then the template is saved to a git repo with a :
# The Omega user doesn't know about the [/path/on/the/server] he just sees the file and directory 's tree inside [/path/on/the/server]. Plsu inside [/path/on/the/server] is where the git clone happened. export FILE_BEING_EDITED_WITH_OMEGA=/path/on/the/server/blackfriday-champions.mjml export COMMIT_MESSAGE="This is the text typed by the omega user, in the dialog box, through his web page, so angular app will fill that one up" git add $FILE_BEING_EDITED_WITH_OMEGA && git commit "$COMMIT_MESSAGE" && git push
- clicks the
Basically here, I soon understood that in order to actually have a Git Persistence Module, I HAD, to implement A RESTful API on a server, that the Angular App wil call :
- Indeed, If I want to persist to a git repo, and collaborate with others, I have to be able to git clone it in a filesystem, inside a system where the git software is installed. I would dream of an implementation of git inside our browsers, using a browser managed virutal filesystem, etc...
- Before we have all that, in our daily web browsers, well the easiest and fastest way to execute git commands, for an Angular 8 App, is to ask a REST ful API (the backend, the server) to do it :
- we need a server with a RESTfull API,
- inside a container which has git installed, why not a
NodeJS / TypeScript / Swagger / https://github.com/steveukx/git-js
stack, - and that way the Angular App can
git clone "FIR_REPO_URI" && git add --all && git commit -m "message from omega user" && git push
, just out of calling anOpenAPI
-compliantRESTful API
.
NVM
,NodeJS
,typescript
,- [
SWAGGER
TOTYPESCRIPT
] I'll usetsoa
, which supports "injecting dependency injection", has good community (secruing support), and works with a template concept (so there's potential customization features here). - [
TYPESCRIPT
TOSWAGGER
] Re-generateJSON
orYAML
config file from Generated Source Code : Alsotsoa
; - [
RxJS/TypeScript
] : using rxjs installed with all modules, including those required for usingRxJS
inTypeScript
, cf. https://rxjs.dev/guide/installation#all-module-types-cjs-es6-amd-typescript-via-npm
About tsoa
, what is perfect for Omega, is that tsoa documents on its root README.md
how to handle file upload with RESTful API generated : https://github.com/lukeautry/tsoa#uploading-files
But there's one more thing
Yeah, I have as goal, to bring in dependency injection :
- As to the Angular Client, well dependency injection is builtin
Angular
- As to my
TypeScript
/ RESTful Endpoints, there is no depndency injection there. I'll bring dependency injection here using http://inversify.io/ (and then I'll try https://github.com/nestjs/nest) .InversifyJS
andNestJS
appeared to be the two most suitable, and mature existing DEpendency Injection Frameworks, for aNodeJS
"server side".
All that dev stack will give us artifacts (packages, stuff, bumblebees, whatever you call them) to deploy. Well we will containerize all those artifacts, to deploy and operate them in production.
So here is our bill of material :
- A single host deployement target, consisting of a Virutal MAchine in which are provisioned
docker
anddocker-compose
. Omega
si provisioned with adocker-compose.yml
file defining the following services :Traefik.io
as reverse proxy AND load balancer- An API Gateway to bind up all Endpoints, and manage subscribe plans, and centralize
OpenAPI
documentation. I'll use an API Gateway calledGravitee.io
- One
docker-compose
service for each and everyREST API Endpoint
- One
docker-compose
service for statically serving the Angular Client from within inside a container endowed with the lightest production-ready HTTP statis server, my first (maybe not last) choice will be analpine:nginx
OCI
base image.
- Aside
Omega
, antoherdocker-compose.yml
will provision infrastructure :Keycloak
to setupO.I.D.C.
(OpenID Connect
) Identity ProviderHashiCorp Vault
, to manage the Certification Authority Chain, in the target PKI, and manage TLS/SSL Certificates granted to each deployedOmega
component.
For a start I'll decompose all tasks in this section of the projet 's root README.md
, before task management / scheduling is managed by a dedicated tool.
- Implement a first RESTfull API Endpoint, using
tsoa
, swagger generator, without anyRxJS
. This Endpoint will be a very simple allowing you to multiply any two positive or null integers. - Add JWT authntication, then OpenID Connect, managed by Keycloak
- Add a new Endpoint, that basically returns a list of text files, among a given set of files, inside a given folder :
- the endpoint will always return a map :
key
being the path of the file, relative to workspace root,- the mapped
value
being the actual files itself as binary content. The workspace is a folder inside theOCI
container, in which theRESTful API
runs.
- This endpoint will later return an additional tree, to manage workspace, and make the RESTful API stateless :
- it will never "be on this file in the workspace", and later "be on this other file in the workspace",
- because the browser's
SPA
will manage that. - That will make the server-side stateless, and the solution scalable-ready for multi-tenancy /multi-users
- and I today believe that it's a definitive classic technique, to "push everything that is stateful to the SPA", in order to make the backend stateless.
- and to make it all stateless, regarding git operatyions : It will be required to [commit, push, and send a merge/pull request], only ONE FILE AT A TIME, so that merge conflicts can always be easy to resolve, displaying a nice diff window in
Angular Material Design
, when it's time to save. So don't see this as an actual workspace, keeping track of the "workspace 's state", which file has been commit, which has not, etc... SO users will never ever be bound to keep working with a given container.
- the endpoint will always return a map :