Skip to content

Content Management System (CMS)

goFrendiAsgard edited this page Apr 16, 2018 · 16 revisions

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>

Creating your own CMS

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:~$ 
    

Start the server

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

CMS File Structure

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.

Configurations

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 or showLeftNav are set to true. 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 either http://, 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, and jargon. 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 &copy; 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.

Routes

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, or DELETE requests. Or you can set a route to serve all requests.

  • Chain

    The CHIML script or chain location to prepare a data that will be shown to the user.

    The chain takes a single parameter state as an input and should return a response object.

  • View

    An ejs template. To determine how your page will be presented to a user. A simplest route can only contains a name, a route, and a view

  • Groups

    Groups of users that are authorized to access the page.

Route Examples

Hello world

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

Hello name (without view)

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

Hello name (with view)

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

ls -al && cal

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:
    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")
    view:
    <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

More about Chain

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 like request and config. 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 or https)
      • url: URL, relative to Base Url
      • query: An object represent the data sent by using get method. In PHP, this property is equal to $_GET
      • body: An object represent the data sent by using methods other than get. 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 have state.request.params.date and state.request.params.title respectively.
      • session: An object represent the session data of the connection. This is equal to $_SESSION in PHP.
    • response: A string or an object 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 the response or defined independently in the route), a JSON representation of the response.data will be sent.
      • view: A string, the view template
      • session: An object, represent the new session data
      • cookies: An object, represent the new cookies data
      • status: HTML status. Typically will be 200 for normal request. If greater or equal to 400, it will yield an error response.
      • errorMessage: A string, the error message
    • 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
  • response

    response can be an object (as defined in state) or can be a string.

    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 corresponding view template.

    However, if the view template is not defined, A JSON representative of response.data will be returned.

CCK

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: cms-flow

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.

Generated API

Below is the routes generated by CCK:

  • GET /api/:version/:schemaName: Select

    SELECT * FROM schemaName WHERE _deleted=0;
  • GET /api/:version/:schemaName.json: Select

  • GET /api/:version/:schemaName?excludeDeleted=0: Select all data, including the deleted ones

    SELECT * FROM schemaName WHERE _deleted=0 OR _deleted=1;
  • GET /api/:version/:schemaName?limit=<limit>&offset=<offset>: Select with limit and offset

    SELECT * FROM schemaName WHERE _deleted=0 LIMIT offset,limit;
  • GET /api/:version/:schemaName?fields=<field-1>,<field-2>,...<field-n>: Select only several field

    SELECT field-1, field-2,... field-n FROM schemaName WHERE _deleted=0;
  • GET /api/:version/:schemaName?k=<keyword>: Select by keyword

    SELECT * 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 sort

    SELECT * FROM schemaName WHERE _deleted=0 ORDER BY field-1 ASC;
  • GET /api/:version/:schemaName?sortedBy=<field-1>&sortOrder=-1: Select and sort

    SELECT * 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 fields

    SELECT * 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 to mongodb 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

Generated pages

  • 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

Creating CCK Entity

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, and Fields
  • Set Privileges
  • You can now access http://localhost:3000/data/students

Add navigation

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

On action

Since you have created an entity and a corresponding menu, Now you can click on Students and start adding data

Other features

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 %>"
      }
    }
  }
}

Plugin

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.