Content Management System (CMS) - goFrendiAsgard/chimera-framework GitHub Wiki
CMS is a web application like wordpress, drupal, or joomla. It support digital content creation and modification. As well as multiple user in a collaborative environment. See this reference for a more detail information about CMS
By using Chimera-Framework you can build your own CMS easily. What you need to do is just invoking
chimera-init-cms <your-project-name>To create your own CMS, you need to do the following steps
-
Ensure every pre-requisites has been already installed in your computer:
if you are using ubuntu or other debian based linux distribution, you can run the following commands in order to get everything installed
sudo apt-get install nodejs npm mongodb sudo npm install --global chimera-framework
-
Create the boilerplate by invoking
chimera-init-cms <your-project-name>in your terminal. If everything works, you will see these output in the terminal:gofrendi@asgard:~$ chimera-init-cms myApp Mongodb Url (mongodb://localhost/myApp): [INFO] Read chimera-framework's package.json... [INFO] Done... [INFO] Clone CMS... Cloning into 'myApp'... [INFO] Done... [INFO] Creating project's package.json... [INFO] Done... [INFO] Creating webConfig.default.js... [INFO] Done... [INFO] Creating webConfig.js... [INFO] Done... [INFO] Performing npm install... npm WARN deprecated [email protected]: Use uuid module instead npm WARN excluding symbolic link test/node_modules/uws/build/Release/uws.node -> /home/gofrendi/myApp/node_modules/.staging/chimera-framework-21728678/chimera-framework/test/node_modules/uws/build/Release/obj.target/uws.node npm WARN prefer global [email protected] should be installed with -g npm WARN optional Skipping failed optional dependency /chokidar/fsevents: npm WARN notsup Not compatible with your operating system or architecture: [email protected] npm WARN [email protected] No repository field. [INFO] Done... [INFO] Performing migration... SuperAdmin username: admin SuperAdmin email: [email protected] SuperAdmin password: admin [INFO] Migration succeed * 0.000-cck up * 0.001-insert-user up * 0.002-default-routes up * 0.003-default-configs up * 0.004-default-groups up Complete... gofrendi@asgard:~$
After the CMS ready, you can start the server by invoking npm start in the terminal
The default port will be 3000. Thus you can access the CMS by openning your browser (I recommend google chrome) and type http://localhost:3000
The structure of the CMS is as follow:
test
├── cck.js # cck helper file
├── chains # chains directory
│ ├── cck # cck-specific chains directory
│ │ ├── core.deleteAction.js
│ │ ├── core.delete.js
│ │ ├── ...
│ │ └── lib.processor.js
│ ├── cck.init.js
│ ├── config.afterSelect.js
│ ├── core.hook.afterRequest.js
│ ├── ...
│ └── user.init.js
├── helper.js # common helper file
├── index.js # CMS start-point file
├── migrate.js # migration tool
├── migrations # migration directory
│ ├── 0.001-insert-user.chiml
│ ├── 0.002-basic-routes.chiml
│ ├── ...
│ └── 0.chiml
├── package.json
├── public # static resources directory
│ ├── css
│ │ ├── style.css
│ │ └── themes
│ │ ├── cerulean.min.css
│ │ ├── cosmo.min.css
│ │ ├── ...
│ │ └── yeti.min.css
│ ├── favicon.ico
│ ├── js
│ │ └── cck-form.js
│ └── uploads
│ ├── 1517487760495saber.jpg
│ ├── 1517487885770lancer.jpg
│ ├── ...
│ └── 1517534859811gaebolg.png
├── README.md
├── test.js # CMS test tool
├── test-web.json # postman-imported tests
├── views # views directory
│ ├── cck # cck-specific views directory
│ │ ├── default.deleteAction.ejs
│ │ ├── default.insertAction.ejs
│ │ ├── ...
│ │ ├── inputs
│ │ │ ├── cckField.ejs
│ │ │ ├── checkBoxes.ejs
│ │ │ ├── ...
│ │ │ └── url.ejs
│ │ └── presentations
│ │ ├── codeText.ejs
│ │ ├── file.ejs
│ │ ├── ...
│ │ └── trimmedText.ejs
│ ├── default.error.ejs
│ ├── default.layout.ejs
│ ├── ...
│ └── partials # layout-partial views directory
│ ├── default.htmlHeader.ejs
│ ├── default.largeBanner.ejs
│ ├── ...
│ └── default.smallFooter.ejs
├── webConfig.js # your custom configuration file
└── webConfig.default.js # default configuration
You are free to add any custom views, css, javascripts, or even templates. But please make sure to not touch the existing ones.
If it is really necessary (in case of you want to add your custom middleware or deal with socket programming), you can modify webConfig.js, since this file won't be overridden when you upgrade the CMS to the newer version.
You can access configuration by clicking Settings | Configurations
Some configurations will merely change the layout of the CMS, while some others will seriously affect how the system works.
Some configurations that you probably like to fiddle up are:
-
title
The title of your CMS. Will be shown in top menu as well as jumbotron. Any valid
stringwill be okay. -
jargon
The jargon of your CMS. Will be shown in the jumbotron. Any valid
stringwill be okay. -
logo
The logo of your CMS. Will be shown in the jumbotron. You can upload any image file here.
-
bootstrapNavClass
Custom
CSSclass for the top menu. The possible values are:- navbar-default (default)
- navbar-default navbar-static-top (default static)
- navbar-default navbar-fixed-top (default fixed)
- navbar-inverse (inverse)
- navbar-inverse navbar-static-top (inverse static)
- navbar-inverse navbar-fixed-top (inverse fixed)
Bootstrap navigation classes consists of two parts.
The first part (default or inverse) allows you to choose between default color or inverse color. If you use the default theme, the default color is white, while the inverse color is black.
The second part ([empty], static, or fixed) define the behavior (CSS position property) of the menu.
-
bootstrapTheme
Custom bootstrap theme, including
cerulean,united,simplex, etc. This will affect the look and feel of your CMS. -
navigation
Two level depth navigation structure in JSON array-of-object format. The navigation menu will be rendered at the top or left part of the page if
showTopNavorshowLeftNavare set totrue. The default value is:[ { "caption": "Home", "groups": ["loggedIn", "loggedOut"], "url": "/" }, { "caption": "Show Case", "groups": ["loggedIn", "loggedOut"], "children": [ { "caption": "Noble Phantasm", "groups": ["loggedIn", "loggedOut"], "url": "/data/hogu" }, { "caption": "Servants", "groups": ["loggedIn", "loggedOut"], "url": "/data/servants" } ] } ]Each navigation can has these properties:
-
caption
A human readable caption. Any valid HTML will be okay. You can even put images here.
-
groups
Define the user groups that can see the menu. If not present, everyone will be able to see the menu.
Please note that navigation has nothing to do with the real page authorization. So, eventhough you set the menu to be accessible by everyone, you will still need to set the permission to access the page separately.
This also means that some pages might be accessible eventhough you don't make it available through the navigation.
-
url
The url of that will be accessible from the navigation menu. Should be preceeded with
/if refer to a local page, and should be preceeded with eitherhttp://,https://, or any other valid protocol if refer to external url -
children
Array-of-object. The sub navigations which are parts of this current navigation menu.
-
-
showTopNav
Determine whether
top navigationshould be visible or not. -
showLeftNav
Determine whether
left navigationshould be visible or not. -
showJumbotron
Determine whether
jumbotronshould be visible or not. -
jumbotron
HTML formatted jumbotron content. By default it will show the
logo,title, andjargon. But you can custom it anyway.The default value is as follow:
<div class="jumbotron col-sm-12"> <div class="col-sm-2"> <img class="col-sm-12" src="<%%= config.logo %>" /> </div> <div class="col-sm-10"> <h1><%%= config.title %></h1> <p><%%= config.jargon %></p> </div> </div>
-
showFooter
Determine whether
footershould be visible or not. -
footer
HTML formatted footer content. The default value is:
<footer style="margin-bottom:20px; text-align:right; font-size:0.8em;"> Chimera web app © 2018-tomorrowMorning </footer>
-
showRightWidget
Determine whether
rightWidgetshould be visible or not. -
rightWidget
HTML formatted Right widget content. You can put
paypal-donation-button,google-advertisementor anything here.
Routing is a very important aspect of any web application. If you came from vanilla PHP, you might be unfamiliar with this concept since PHP automatically map the physical file location to the URL.
This is pretty convenient since you can just create a new file in order to make a new page. However it come with a drawback. For example, a hacker can simply determine what framework you are using by accessing the urls. The most common scenario is, a hacker will try to access /wp-admin. If it works, than the hacker can be sure if you are using wordpress.
However, if you are already familiar with other framework like Laravel, Django, or express, you might already understand the concept of routing.
To manage your CMS routes, you can access Settings | Routes
A route consists of several properties:
-
Name
The name of the route. Make sure it is something self-explaining, like
Landing Page,Front Page,About Page, etc.Route name will not rendered by the system. It just help you to understand what the current route do.
-
Route
The url of the route. Should be prepended with
/. -
Method
The HTTP method of the route. You can determine whether a route is only serve
GET,POST, orDELETErequests. Or you can set a route to serveallrequests. -
Chain
The
CHIMLscript orchainlocation to prepare a data that will be shown to the user.The
chaintakes a single parameterstateas an input and should return aresponseobject. -
View
An
ejstemplate. To determine how your page will be presented to a user. A simplest route can only contains aname, aroute, and aview -
Groups
Groups of users that are authorized to access the page.
The simplest route example is no other than typical hello world application. In order to create a hello world page, you can make a new route and set these properties:
- name:
hello world - route:
/hello - view:
<p>hello world</p>
You can access the page by typing http://localhost:3000/hello
To make a more dynamic page, you can use parameters and set your route's property as follow:
- name:
hello world (without view) - route:
/hello/:name - chain:
ins: state out: response do: |response.data <-- ("Hello " + state.request.params.name)
You can access the page by typing http://localhost:3000/hello/Tony
One of common best practice is to separate the data from the view. Considering the previous example, you can make a better approach by setting your route as follow:
- name:
hello world (with view) - route:
/hello-view/:name - chain:
ins: state out: response do: |response.data.name <-- (state.request.params.name)
- view:
<p>Hello <%= name %></p>
You can access the page by typing http://localhost:3000/hello/Tony
One of Chimera-Framework selling point is you can use any CLI commands/programs as part of your application. So that it will slightly reduce your development time.
In this example, we will try to read dir and year query request and use it to run ls and cal respectively.
ls is a Unix builtin tool to view the directory, while cal is a Unix program to show a calender (either monthly or anually)
You can set your route as follow:
- name:
test - route:
/test - chain:
view:
ins: state out: response do: - |get <-- (state.request.query) - |(get.dir) -> ls -al -> response.data.list - |(get.year) -> cal -> response.data.calendar - |response.partial.rightWidget <-- ("This is the right widget")
<h1>ls -al</h1> <pre><%= list %></pre> <h1>cal</h1> <pre><%= calendar %></pre>
You can access the page by typing http://localhost:3000/test?dir=.&year=2018 in your address bar
A Route chain can be a CHIML script (written directly in the text area), or the physical location of either CHIML or Javascript file acting as chain.
Brief example of CHIML script can be found here, while the detail specification can be found here
Writing the chain in Javascript will make the chain run faster. However whenever you modify the chain, you need to reload the server. On the other hand, writing the chain in CHIML script make the chain more flexible and arguably more readable. But it is going to be a bit slower.
Your Javascript chain should export a function with three parameters, inputs, vars, and callback. Below is a simple Javascript chain example:
Modules.export = (ins, vars, callback) => {
let state = ins[0]
let name = state.request.query['name']
callback(null, 'hello ' + name)
}-
state
A
statecontains of several keys likerequestandconfig. Below is the complete structure of a state:-
config: An
objectrepresent CMS configuration -
request: An
objectrepresent request from the client-
auth: An
objectrepresent authentication state- username: Current user's username
- email: Current user's email
-
groups: An
arrayrepresent current user's groups - id: Current user's id
- iat: iat
- exp: expired time
- baseUrl: Base URL
- hostname: Hostname
-
method: HTTP method (
get,post,put, etc) -
protocol: HTTP protocol (
httporhttps) - url: URL, relative to Base Url
-
query: An
objectrepresent the data sent by usinggetmethod. In PHP, this property is equal to$_GET -
body: An
objectrepresent the data sent by using methods other thanget. In PHP, this property is roughly equal to$_POST -
cookies: An
objectrepresent cookies sent by the client. In PHP, this is equal to$_COOKIES -
files: An
objetrepresent files sent by the client. In PHP, this property is roughly equal to$_FILES -
params: An
objectrepresent the URL parameters. For example if you have/blog/:date/:titleas route url, you will havestate.request.params.dateandstate.request.params.titlerespectively. -
session: An
objectrepresent the session data of the connection. This is equal to$_SESSIONin PHP.
-
auth: An
-
response: A
stringor anobjectrepresent the temporary response sent to the client.-
data: An
objectsent to the view to be rendered. If the view is not defined (either as part of theresponseor defined independently in the route), a JSON representation of theresponse.datawill be sent. -
view: A
string, theviewtemplate -
session: An
object, represent the newsessiondata -
cookies: An
object, represent the newcookiesdata -
status: HTML status. Typically will be
200for normal request. If greater or equal to400, it will yield an error response. -
errorMessage: A
string, the error message
-
data: An
-
matchedRoute: The matching
route- route: Express route pattern
- method: HTTP method
- chain: Chain of the route
- groups: Authorized groups to access the route
- view: Ejs template
-
config: An
-
response
responsecan be an object (as defined instate) or can be astring.If the
responseis a string, it will be sent directly to the client.If the
responseis an object, it will be rendered in the correspondingviewtemplate.However, if the
viewtemplate is not defined, A JSON representative ofresponse.datawill be returned.
CCK stands for Content Construction Kit. It is a tool to help you creating a CRUD application quickly.
CCK is not a code generator. No code generated when you make a new entity. This mean, whenever you modify your CCK entity, the changes will be reflected immediately.
CCK will work in most use cases. Technically, you can even use CCK to build CCK.
In CCK you can compose several chains into API-Chain. The general architecture is shown below:

Under the hood, CCK will dynamically create several routes and apply it immediately. Aside from the visual pages, The routes will also serve REST API as well. This is going to be useful in case of you want to create a mobile/desktop application use Chimera-Framework as the backend.
Below is the routes generated by CCK:
-
GET
/api/:version/:schemaName: SelectSELECT * FROM schemaName WHERE _deleted=0;
-
GET
/api/:version/:schemaName.json: Select -
GET
/api/:version/:schemaName?excludeDeleted=0: Select all data, including the deleted onesSELECT * FROM schemaName WHERE _deleted=0 OR _deleted=1;
-
GET
/api/:version/:schemaName?limit=<limit>&offset=<offset>: Select withlimitandoffsetSELECT * FROM schemaName WHERE _deleted=0 LIMIT offset,limit;
-
GET
/api/:version/:schemaName?fields=<field-1>,<field-2>,...<field-n>: Select only several fieldSELECT field-1, field-2,... field-n FROM schemaName WHERE _deleted=0;
-
GET
/api/:version/:schemaName?k=<keyword>: Select by keywordSELECT * FROM schemaName WHERE _deleted=0 AND (field-1 LIKE '%keyword%' OR field-2 LIKE '%keyword%'... OR field-n LIKE '%keyword%');
-
GET
/api/:version/:schemaName?sortedBy=<field-1>&sortOrder=1: Select and sortSELECT * FROM schemaName WHERE _deleted=0 ORDER BY field-1 ASC;
-
GET
/api/:version/:schemaName?sortedBy=<field-1>&sortOrder=-1: Select and sortSELECT * FROM schemaName WHERE _deleted=0 ORDER BY field-1 DESC;
-
GET
/api/:version/:schemaName?sort={"<field-1>":1, "<field-2>":1}: Select and sort by multiple fieldsSELECT * FROM schemaName WHERE _deleted=0 ORDER BY field-1 ASC, field-2 ASC;
-
GET
/api/:version/:schemaName?q={"$or":[{"<field-1>":"<val-1>"},{"$and":[{"<field-2>":"val-2"},{"<field-3>":"val-3"}]}]}: Select and define query (Please refer tomongodb filter)SELECT * FROM schemaName WHERE _deleted=0 AND (field-1='val-1' OR (field-2='val-2' AND field-3='val-3'));
-
POST
/api/:version/:schemaName: Insert (fields and values should be in request body) -
PUT
/api/:version/:schemaName: Update (fields and values should be in request body) -
DELETE
/api/:version/:schemaName: Delete -
GET
/api/:version/:schemaName/:id: Select by id -
GET
/api/:version/:schemaName.json/:id: Select by id -
POST
/api/:version/:schemaName/:id: Insert with predefined id -
PUT
/api/:version/:schemaName/:id: Update by id -
DELETE
/api/:version/:schemaName/:id: Delete by id
- ALL
/data/:schemaName: Tabular view - ALL
/data/:schemaName/:id: Detail view - ALL
/data/:schemaName/insert: Insert Form - POST
/data/:schemaName/insert: Insert Action - ALL
/data/:schemaName/update: Update Form - POST
/data/:schemaName/update: Update Action - ALL
/data/:schemaName/delete: Delete Form - POST
/data/:schemaName/delete: Delete Action
You can do the following steps in order to create new CCK Entity:
- Login
- Click
Settings|Content Construction Kit - Add new entity by clicking
+button
- Set
Name,Collection Name,Caption, andFields
- Set
Privileges
- You can now access
http://localhost:3000/data/students
Rather than typing the URL, it is more convenient to just click a menu. You can do the following steps in order to create a new menu:
- Click
Settings|Configurations - Add
Navigation
Since you have created an entity and a corresponding menu, Now you can click on Students and start adding data
CCk can also handle nested documents as well as typical RDBMS relationships
- Many2One (e.g: Many students are in the same faculty)
- One2Many (e.g: A student has many hobbies)
- One2Many + Many2One = Many2Many (e.g: A student take many courses, while a single course is taken by many students)
Below is the example of CCk entity's field definition for Many2One, One2Many, and Many2Many:
{
"picture": {
"inputTemplate": "<%- cck.input.image %>",
"presentationTemplate": "<%- cck.presentation.image %>"
},
"name": {
"inputTemplate": "<%- cck.input.text %>",
"presentationTemplate": "<%- cck.presentation.text %>"
},
"expertise": {
"inputTemplate": "<%- cck.input.option %>",
"presentationTemplate": "<%- cck.presentation.option %>",
"options": {
"ninjutsu": "Ninjutsu",
"genjutsu": "Genjutsu",
"taijutsu": "Taijutsu"
}
},
"faculty": {
"inputTemplate": "<%- cck.input.many2one %>",
"presentationTemplate": "<%- cck.presentation.many2one %>",
"ref": "faculties",
"keyField": "name",
"fields": [
"name"
]
},
"hobbies": {
"inputTemplate": "<%- cck.input.one2many %>",
"presentationTemplate": "<%- cck.presentation.one2many %>",
"fields": {
"name": {
"inputTemplate": "<%- cck.input.text %>",
"presentationTemplate": "<%- cck.presentation.text %>"
}
}
}
}Simple plugin example can be found on this repository.
You can copy the folder and modify package.json as necessary.
Basically, a plugin is a collection of localized migrations, chains, views, and public assets. You can put any necessary migrations in migrations folder. Make sure the name of your migration file is unique.






