-
Notifications
You must be signed in to change notification settings - Fork 8
Content Management System (CMS)
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
string
will be okay. -
jargon
The jargon of your CMS. Will be shown in the jumbotron. Any valid
string
will be okay. -
logo
The logo of your CMS. Will be shown in the jumbotron. You can upload any image file here.
-
bootstrapNavClass
Custom
CSS
class 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
showTopNav
orshowLeftNav
are 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 navigation
should be visible or not. -
showLeftNav
Determine whether
left navigation
should be visible or not. -
showJumbotron
Determine whether
jumbotron
should 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
footer
should 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
rightWidget
should be visible or not. -
rightWidget
HTML formatted Right widget content. You can put
paypal-donation-button
,google-advertisement
or 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
, orDELETE
requests. Or you can set a route to serveall
requests. -
Chain
The
CHIML
script orchain
location to prepare a data that will be shown to the user.The
chain
takes a single parameterstate
as an input and should return aresponse
object. -
View
An
ejs
template. 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
state
contains of several keys likerequest
andconfig
. Below is the complete structure of a state:-
config: An
object
represent CMS configuration -
request: An
object
represent request from the client-
auth: An
object
represent authentication state- username: Current user's username
- email: Current user's email
-
groups: An
array
represent 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 (
http
orhttps
) - url: URL, relative to Base Url
-
query: An
object
represent the data sent by usingget
method. In PHP, this property is equal to$_GET
-
body: An
object
represent the data sent by using methods other thanget
. In PHP, this property is roughly equal to$_POST
-
cookies: An
object
represent cookies sent by the client. In PHP, this is equal to$_COOKIES
-
files: An
objet
represent files sent by the client. In PHP, this property is roughly equal to$_FILES
-
params: An
object
represent the URL parameters. For example if you have/blog/:date/:title
as route url, you will havestate.request.params.date
andstate.request.params.title
respectively. -
session: An
object
represent the session data of the connection. This is equal to$_SESSION
in PHP.
-
auth: An
-
response: A
string
or anobject
represent the temporary response sent to the client.-
data: An
object
sent to the view to be rendered. If the view is not defined (either as part of theresponse
or defined independently in the route), a JSON representation of theresponse.data
will be sent. -
view: A
string
, theview
template -
session: An
object
, represent the newsession
data -
cookies: An
object
, represent the newcookies
data -
status: HTML status. Typically will be
200
for 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
response
can be an object (as defined instate
) or can be astring
.If the
response
is a string, it will be sent directly to the client.If the
response
is an object, it will be rendered in the correspondingview
template.However, if the
view
template is not defined, A JSON representative ofresponse.data
will 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 withlimit
andoffset
SELECT * 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.