You'll need the following tools installed on your computer
npm install foxxy -g
npm install foxx-cli -g
# optional for using your own code editor
npm install fasty-cli -g
You'll have to update your ~/.foxxrc file with
[server.foxxy]
url=http://localhost:8529
username=root
password=password
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 run --rm cms moonc **/*.moon
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.localhost:8080 and http://demo.localhost:8080/static/admin . The database is running on http://localhost:8529 (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 foxxy --database db_demo
foxxy upgrade --server foxxy --database db_demo
You should be able now to access the admin URL with the following credentials :
demo@foxxy.ovh :: 977cebdd
Don't forget to change this default account via the /static/admin page !
Fasty allows you to store your assets in the google cloud. For that you just need to create a folder called `/certs` in the root of your application. For your multi-tenant apps you can define one file per sub domain.
./certs
-- cms.json
-- app1.json
-- default.json
it will fallback to `default.json` if no match. Here a sample of the json data :
{
"type": "service_account",
"project_id": "<your_project_id>",
"private_key_id": "1234567890abcdef",
"private_key": "-----BEGIN PRIVATE KEY-----\n ... <your_private_key>\n-----END PRIVATE KEY-----\n",
"client_email": "client_email",
"client_id": "client_id",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "<link_to_your_file>"
}
Layouts allow you to define the global template of your application. You can have multiple layouts. Each pages can be linked to any layout.
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
{{ 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 }}
{{ 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 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 }
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
Display partials via it's slug
{{ partial | <partial_name> (| <dataset> | <options>) }}
But a partial can have a dataset (arango, rest or use_params) 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 }}
{{ partial | pagination | use_params | current_page#1#pages#10 }}
[string "etlua"]:5: attempt to index field '_params' (a nil value)
Display the current lang
{{ lang }}
Display an element set in the settings/home section (json)
{{ settings | <mailgun_api> }} will fetch data from json data
DIsplay a single page application
{{ spa | <my-app> }}
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)
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: welcome
Display a value from the splat params (see pages section)
{{ splat | <splat_key> }}
Display a helper (AQL + partial). You can now add options
{{ helper | <helper_name> (| key1#value1#key2#value2...) }}
If you need to execute a request just use
{{ aql | <your_aql_request> }}
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. You can also define a default value if og_data is not provided or empty.
{{ og_data | title | <default> }}
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> }}
If you need to load an external JSON resource and extract / display a specific key you need to use :
{{ json | url | key }}
The key can be a nested one (e.g owner.login)
Display a specific key from a dataset
{{ dataset | key | field | <args> }}
Can be useful for loading JS or CSS content from a dataset record
e.g. :
{{ dataset | slug=myscript | js }}
The second one will return the URL only as js file (using the right content-type)
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 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 }}
{{ 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)
})
Store your AQL requests and then use them within Helpers
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
Choose a partial, a request ... Then you can call it anywhere you want using the helper shortcut
Redirections allow you to define a specific route for a specific helper
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: