Documentation

Fasty is a Blazing Fast CMS built on top of openresty & arangoDB

Requirements

You'll need the following tools installed on your computer

  • NodeJS
  • NPM or Yarn
  • docker & docker-compose
  • foxxy NPM module installed globally
npm install foxxy -g

You'll have to update your ~/.foxxrc file with 

[server.fasty]
url=http://localhost:8530
username=root
password=password

Get Started

Fasty is based on Openresty, Lua & ArangoDB. You'll have the full stack installed via Docker. Pull the repository and inside it run :

docker-compose build
docker-compose up cms

You have first to create a foxxy/app/js/config.js file (you can copy content from foxxy/app/js/config.js.sample)

The application should run on http://demo.127.0.0.1.xip.io:8080 and http://demo.127.0.0.1.xip.io:8080/static/admin . The database is running on http://127.0.0.1:8530 (login : root, password: password)

You'll need to create first a database called db_demo and run within the foxxy folder

foxxy upgrade settings --server fasty --database db_demo
foxxy upgrade --server fasty --database db_demo

You should be able now to access the admin URL with the following credentials : 

[email protected] :: 977cebdd

Layouts

Layouts allow you to define the global template of your application. You can have multiple layouts. Each pages can be linked to any layout.

<!DOCTYPE html>
<html>
  <head lang="{{ lang }}">
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    @headers
    <link rel="stylesheet" href="@css_vendors" />
    <link rel="stylesheet" href="@css" />
  </head>
  <body>
    {{ partial | nav }} 
    
    
    
    {{ partial | footer }}
    <script src='@js_vendors'></script> 
    <script src='@js'></script> 
  </body>
</html>

In this basic sample we can see interesting things :

@css & @css_vendors are CSS content defined in the layout
@js & @js_vendors are JS content defined in the layout

@headers will be replaced by generated headers

will display the content of your page

you can also notice the en to display the current language & some {{ partials | ... }} to display shared parts

@css_vendors :

{{ external | https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css }}
{{ external | https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/styles/railscasts.min.css }}

@js_vendors

{{ external | https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/highlight.min.js }}
{{ external | https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.6/languages/json.min.js }}
{{ external | https://cdnjs.cloudflare.com/ajax/libs/riot/3.13.2/riot+compiler.min.js }}

You can define as many external resources you need.

Pages

Pages are the pages of your website. You can create as many pages you want.

Notice that the slug must be unique across all pages. The default url schema is : /:lang/:all/:slug(/*)

:lang is the current lang (e.g. en, fr, etc.)
:all can be anything you want (e.g. -, my, anything, useful, etc.)
:slug
is your page's slug (e.g. home, page, etc)

Any additional path element will be stored in the splat params.
Sample : /en/my/home/page/10

splat params will looks like { page: 10 }


Shorcuts

page

Display any page content via it's slug

{{ page | <slug_or_field> (| <dataset>) }}

{{ page | my_famous_page }} << Display page content of the my_famous_page page

{{ page | slug | posts }} << This one will display the html field from your posts dataset using the slug defined in the URL


partial

Display partials via it's slug

{{ partial | <partial_name> (| <dataset> | <options>) }}

But a partial can have a dataset (arango, rest) and you can also use do_not_eval as dataset if you just want to include etlua file without evaluating it directly.

{{ partial | demo | arango | aql/FOR user IN users RETURN user }}

lang

Display the current lang

{{ lang }}

settings

Display an elemnt set in the settings/home section (json) 

{{ settings | <mailgun_api> }} will fetch data from json data

spa

DIsplay a single page application

{{ spa | <my-app> }}

riot

Mount a riot tag.

{{ riot | tag1 }} Load the widget but don't mount it

{{ riot | tag1 | mount }} Load and mount the widget

{{ riot | tag1#tag2 }} Load tag1 & tag2 (reduce http calls)

tr

Display a translated text. If the key don't exist it will create an entry in the traductions list.

{{ tr | welcome }}

It will display a missing message if the translation is not existing:  Missing translation welcome

splat

Display a value from the splat params (see pages section)

{{ splat | <splat_key> }}

helper

Display a helper (AQL + partial). You can now add options

{{ helper | <helper_name> (| key1#value1#key2#value2...) }}

AQL

If you need to execute a request just use

{{ aql | <your_aql_request> }}

og_data

You can define a request per page to define headers dynamically. 

Let say we have this AQL request defined : 

FOR doc IN datasets
  FILTER doc.type == "articles" AND doc.slug == @slug
  RETURN { title: doc.title }

You could use then this shortcut in any header fields to get the content.

{{ og_data | title }}

html

To display a content from a dataset within the page partial.

{{ html | <key> | <field> }}

You can also use the og:aql request to get the json object to be transformed within the page partial

{{ html | <key_defined_in_og:aql> }}

Dataset

Display a specific key from a dataset

{{ dataset | key | field }}

Can be useful for loading JS or CSS content from a dataset record

e.g. {{ dataset | js | slug=myscript }}

Components

Components are JS components (RiotJS for now)

<todo>

  <!-- layout -->
  <h3>{ opts.title }</h3>

  <ul>
    <li each={ item, i in items }>{ item }</li>
  </ul>

  <form onsubmit={ add }>
    <input ref="input">
    <button>Add #{ items.length + 1 }</button>
  </form>

  <!-- style -->
  <style>
    h3 {
      font-size: 14px;
    }
  </style>

  <!-- logic -->
  <script>
    this.items = []

    add(e) {
      e.preventDefault()
      var input = this.refs.input
      this.items.push(input.value)
      input.value = ''
    }
  </script>

</todo>

Partials

Partials are .etlua files ... It can be a simple raw HTML content or can embed some logic via LUA code.

You can call a partial with a specific dataset (AQL request, or external JSON data)

<div class="field is-grouped is-grouped-multiline">
  <% for k, item in pairs(dataset.results) do %>
  <div class="control">
    <div class="tags has-addons">
      <span class="tag"><%= item.name[lang] %></span>
      <span class="tag is-primary"><%= item.state[lang] %></span>
    </div>
  </div>
  <% end %>
</div>

With the following AQL request

FOR doc IN datasets
FILTER doc.type == 'features'
SORT doc.order ASC
RETURN doc

You can define a helper called features to load this partial & AQL and then call this helper with {{ helper | features }}

Single Page Applications

Create as many SPA you want. An application has on HTML tag to load components and one JS tag to set the router
{{ riot | account#plans#tchins#offer_tchins#tchins_history#friends#pending_invitations#pending_approvals#blocked }}

<div id="app"></div>
document.addEventListener('DOMContentLoaded', function() {
  route('/', function(name) { riot.mount('div#app', 'account') })
  route('/plans', function(name) { riot.mount('div#app', 'plans') })
  route('/tchins', function(name) { riot.mount('div#app', 'tchins') })
  route('/offer_tchins', function(name) { riot.mount('div#app', 'offer_tchins') })
  route('/tchins_history', function(name) { riot.mount('div#app', 'tchins_history') })
  route('/friends', function(name) { riot.mount('div#app', 'friends') })
  route('/pending_invitations', function(name) { riot.mount('div#app', 'pending_invitations') })
  route('/pending_approvals', function(name) { riot.mount('div#app', 'pending_approvals') })
  route('/blocked', function(name) { riot.mount('div#app', 'blocked') })
  route.start(true)
})

AQLs

Store your AQL requests and then use them within Helpers

Datatypes & Datasets

Datatypes allow you to create dynamic forms for the content editors.

{
  "model": [
    { "r": true, "c": "1-1", "n": "name", "t": "string", "j": "joi.string().required()", "l": "Name", "tr": true },
    { "r": true, "c": "1-1", "n": "state", "t": "string", "j": "joi.string().required()", "l": "State", "tr": true }
  ],
  "columns": [ { "name": "name", "tr": true } ],
  "sortable": true
}

Once a dataset defined; a new dataype will appear and you'll be able to create & manage your data

Helpers

Choose a partial, a request ... Then you can call it anywhere you want using the helper shortcut

Redirections

Redirections allow you to define a specific route for a specific helper

API Builder

The API Builder allow you to write your Foxx service directly via Fasty. You can also deploy it with one click !

Server side scripts

You can edit your Nodejs server side scripts and restart them directly from Fasty. You'll have first to set the docker-compose file :
chatroom:
  build:
    context: .
    dockerfile: Dockerfile_node
  command: /bin/bash -c "yarn && yarn start"
  restart: always
  ports:
    - 8000:8000
  volumes:
    - ./scripts/your_folder/chatroom/:/workspace
  links:
    - arangodb:arangodb
  networks:
    frontend: