Browse Source

docs

master
glmdev 2 years ago
commit
203753cf9d
  1. 3
      .gitignore
  2. 29
      doc/pages/Adding Custom App Modifications.md
  3. 33
      doc/pages/Getting Started 0: Get Set Up.md
  4. 91
      doc/pages/Getting Started 1: Configuration.md
  5. 163
      doc/pages/Getting Started 2: Routing.md
  6. 30
      doc/pages/Getting Started 3: Views & Static Assets.md
  7. 76
      doc/pages/Getting Started 4: Database & ORM.md
  8. 82
      doc/pages/Getting Started 5: Controllers.md
  9. 35
      doc/pages/Getting Started 6: The flitter Command.md
  10. 23
      doc/pages/Getting Started 7: Production.md
  11. 26
      doc/pages/Miscellaneous Info.md
  12. 16
      doc/pages/Navigation.md
  13. 55
      doc/pages/Overview: flitter-agenda.md
  14. 12
      doc/pages/Overview: flitter-auth.md
  15. 29
      doc/pages/Overview: flitter-cli.md
  16. 114
      doc/pages/Overview: flitter-forms.md
  17. 34
      doc/pages/Overview: flitter-less.md
  18. 101
      doc/pages/Overview: flitter-upload.md
  19. 23
      doc/pages/Overview: libflitter.md
  20. 15
      generate
  21. 32
      jsdoc.json
  22. 13
      package.json
  23. 123
      yarn.lock

3
.gitignore

@ -0,0 +1,3 @@
clone/*
node_modules/*
out/*

29
doc/pages/Adding Custom App Modifications.md

@ -0,0 +1,29 @@
From time to time, it may be useful for you (the developer) to add custom functionality to Flitter during initialization. This may be custom Express add-ons, tweaking settings of the underlying Express app, or any number of other things. As such, Flitter comes with a `MiscUnit` for these customizations. This unit in the file `app/MiscUnit.js` and follows the exact same structure as every other unit. It is loaded _after_ the core Flitter units, but _before_ any secondary or custom units. See `libflitter/Unit` for more information on how to add functionality to the unit.
#### Example: Adding the `express-graphql` Middleware
As an example, we'll configure the underlying Express app to use the [express-graphql](https://www.npmjs.com/package/express-graphql) package on the route `/graphql`. To do this, modify the `app/MiscUnit.js` file:
```js
const Unit = require('libflitter/Unit')
class MiscUnit extends Unit {
go(app, context){
// Get the GraphQL package
const graphqlHTTP = require('express-graphql')
// Tell the underlying express middleware to use it
app.express.use('/graphql', graphqlHTTP({
schema: MyGraphQLSchema,
graphiql: true
}))
}
}
module.exports = exports = MiscUnit
```
Here, we modify the `go()` method. This method is passed an instance of the `FlitterApp` application, which we can then use to access the Express server instance like normal.

33
doc/pages/Getting Started 0: Get Set Up.md

@ -0,0 +1,33 @@
Flitter runs on Node.js and is developed over on [my Git server](https://git.glmdev.tech/flitter). Flitter and its sub-components developed by Garrett Mills are licensed under the highly permissive MIT License. In not so many words, this means that you can do basically whatever you want with the Flitter code as long as you absolve me from liability and warranty. [Check out the full license here though.](https://choosealicense.com/licenses/mit/)The master branch always contains a working version of Flitter, so getting started is as easy as cloning the main Flitter repository, installing the Node.js packages, and copying over the default configuration.
#### 1. Clone the Repository
First, pull down a copy of Flitter into a new folder for your project. You can download a zip of the repository [here](https://git.glmdev.tech/flitter/flitter/archive/master.zip), but the easiest way is to use [Git](https://git-scm.com/):
`git clone https://git.glmdev.tech/flitter/flitter <project_name>`
This will create a new directory called `<project_name>` and download Flitter into it.
#### 2. Install the Packages
Flitter relies on a lot of awesome Node.js packages to run. These can be installed using some flavor of Node.js package manager, which are available on basically every platform. Flitter recommends [Yarn](https://yarnpkg.com/en/) as a good option here. In fact, Flitter's creator uses Yarn, so a stable `yarn.lock` file is included with Flitter. Install the packages by running the following command from inside the project folder:
`yarn install`
#### 3. Copy the Default Config
Flitter uses `.env` based environment configuration, which we'll cover more in-depth later, but for now, just copy over the included example configuration:
`cp example.env .env`
#### 4. Start Flitter!
That's all it takes to get Flitter ready to run. You can start the development server using the `./flitter` command:
`./flitter up`
That should start Flitter running on port `8000` by default.
**Next: [1: Configuration](tutorial-Getting Started 1_ Configuration.html)**

91
doc/pages/Getting Started 1: Configuration.md

@ -0,0 +1,91 @@
It's important to get your configuration settings right when using Flitter. Flitter behaves differently in development mode than it does running in production -- this includes exposing error messages and stack traces that could reveal sensitive information about your application. In this section, we'll take a look at how Flitter handles configuration, and the best way to customize it.
#### Flitter Config: A Two-Layer Approach
Configuration in Flitter is loaded in two steps. Environment-specific config is done by changing values in a `.env` file in the root of the application. This file is never, ever committed to version control and should not be published with your app. It includes things like the database credentials, crypto key, and environment settings.
Flitter ships with a sample `example.env` file that you can customize. Just `cp example.env .env` and fill in your information. It's important that you replace the `SECRET` variable with a random value, as it's used by Flitter to encrypt things like password hashes. Here's a sample:
```bash
SERVER_PORT=8000
LOGGING_LEVEL=1
DATABASE_HOST=127.0.0.1
DATABASE_PORT=27017
DATABASE_NAME=flitter
DATABASE_AUTH=true
DATABASE_USER=flitter
DATABASE_PASS=flitter
SECRET=changeme
ENVIRONMENT=development
```
All other configuration is defined in `.config.js` files in the `config/` folder. Here's a sample `server.config.js`:
```js
const server_config = {
port: process.env.SERVER_PORT || 80,
environment: process.env.ENVIRONMENT || "production",
logging: {
level: process.env.LOGGING_LEVEL || 1
},
session: {
secret: process.env.SECRET || "changeme"
}
}
module.exports = server_config
```
All of the `process.env` bits are references to values that are specified in the `.env` file, but they have defaults, just in case you forget to specify the environment variables. When the framework starts, these configuration files are loaded and are stored in memory based on the name of the file. Then, you can access them globally using the `config()` function. Note that the name of the **file** defines the name of the configuration, not the actual variable name. This is true of most things with Flitter. Here's an example:
> Note that the sample configuration file was called `server.config.js`, so we access the configuration called `server`.
```js
(flitter)> config('server')
// returns:
{ port: 80,
environment: 'production',
logging: { level: 1 },
session: { secret: 'changeme' } }
(flitter)> config('server.port')
// returns:
80
```
(That bit there was the Flitter shell, if you were curious. It allows you to interact with Flitter directly. You can get to it by running `./flitter shell`.)
Flitter takes into account sub-directories when naming configuration as well. Say you had a configuration file `config/auth/registration.config.js`. Flitter would note the `auth/` sub-directory, and you would access this configuration using `config('auth:registration')`.
> The use of the `:` character is an important Flitter convention. Flitter parses reference names from file paths relative to their base directory. When referencing folders, use the `:` character to separate them. For instance, `auth/login/error` becomes `auth:login:error`. Internally, this is referred to as a "Flitter canonical name."
#### Creating Custom Configuration Files
You can add custom configuration files to your application, and they will be loaded by Flitter and made available globally like any other configuration file. To create a new config file, you can use `./flitter`, Flitter's development CLI tool, to create a new configuration file from the built-in template:
`./flitter new config subdirectory:file_name`
This will generate the following file, `config/subdirectory/file_name.config.js`:
```js
// file_name Configuration
const file_name = {
// variable: process.env.VARIABLE || 'default value',
}
module.exports = file_name
```
Now you can specify the configuration values you want, then the configuration can be referenced from `config('subdirectory:file_name')`.
**Next: [2: Routing](tutorial-Getting Started 2_ Routing.html)**

163
doc/pages/Getting Started 2: Routing.md

@ -0,0 +1,163 @@
### Defining Routes
Flitter routes are defined within files inside the `app/routing/routers/` folder. For those familiar with MVC frameworks, each file is a route group (which can have its own prefix), and middleware can be applied to individual routes, groups, or globally.
Flitter comes with a default routes file, `app/routing/routers/index.routes.js` which shows the structure of a Flitter routes file:
```js
const index = {
prefix: '/',
middleware: [
_flitter.mw('HomeLogger'),
],
get: {
'/': [ _flitter.controller('Home').welcome ],
},
post: {
},
}
module.exports = exports = index
```
- `prefix` defines the route prefix
- e.g. if you define `/home` in the file with a prefix of `/user`, then the final route will be `/user/home`.
- `middleware` is an array of middleware to be applied, in order, to all routes in the file.
- Middleware should be referenced using the built-in `_flitter.mw()` function. More on that later.
- `get` defines routes for the `GET` method, and `post` for the `POST` method
- These should be objects where the key is the route's URI, and the value is an array of functions to be applied, in order, when the route is handled. Route-specific middleware should be included here, also using `_flitter.mw()`.
When Flitter starts, it loads each routes file and creates an Express Router for each one, with the specified prefix. Group middleware is applied to the router, and then each route is registered.
#### Creating Custom Route Files
Custom route files can be created and placed in the `app/routing/routers/` folder. As long as they conform to the form required, they will be loaded by Flitter when the application is started. You can create a new route file from the template using a `./flitter` command:
`./flitter new router file_name`
Which will create the file `routing/routers/file_name.routes.js`. **Currently, Flitter does not support sub-directories for route definitions.** This means that route files placed in sub-directories within `routing/routers/` will not be loaded by Flitter.
### Middleware
Middleware in Flitter is defined in the `app/routing/middleware/` directory. The middleware's class should contain a method `test()` which takes 3 variables: the Express request, the Express response, and the function to be called to continue execution of the Flitter stack. Here's an example:
```js
class RequireAuth {
test(req, res, next){
if ( req.session.auth.user ) ){
// call the next function in the stack
next()
}
else {
req.session.destination = req.originalUrl
return res.redirect('/auth/login')
}
}
}
module.exports = RequireAuth
```
Here, if there is a user loaded in the session (which would mean that the user is logged in), then we allow the request to continue. Otherwise, we redirect to the login page. The `next()` call is very important because it keeps the chain of function calls that execute the middleware running.
#### Using Middleware
Middleware can be applied in three ways: globally, per group, or per route. Global middleware is applied to every request Flitter handles, group middleware is applied to all routes in a given routes file, and route middleware is applied to a single route.
Middleware loaded by Flitter should be accessed using the global `_flitter.mw()` function. This function takes the middleware name as an argument and returns the test function that is applied to the request. **Middleware names follow Flitter's convention for sub-directories.** So, if you have a middleware file `app/routing/middleware/auth/RequireAuth.middleware.js`, it can be accessed through the `_flitter.mw()` function like so: `_flitter.mw('auth:RequireAuth')`.
##### Global Middleware
Middleware can be applied globally by adding it to the array of middleware in `app/routing/Middleware.js`. The middleware in this file is applied in order to every request Flitter handles. An example:
```js
// app/routing/Middleware.js
const Middleware = [
_flitter.mw('RouteLogger'),
_flitter.mw('RateLimiter'),
]
module.exports = exports = Middleware
```
Here, the `RouteLogger` middleware is applied, then the `RateLimiter` middleware is applied. These will execute in that order for every request.
##### Group Middleware
Middleware can be applied to all routes in a group. In Flitter, each routes file is a group. Therefore, middleware can be applied to all routes in a given file by adding it to the `middleware` array. For example:
```js
// app/routing/routers/index.routes.js
const index = {
prefix: '/',
middleware: [
_flitter.mw('HomeLogger'),
],
get: {
'/': [ _flitter.controller('Home').welcome ],
},
}
module.exports = exports = index
```
Here, the `HomeLogger` middleware will be applied, in order, to all routes specified in the file, regardless of request method.
##### Route Middleware
Finally, middleware can be applied to individual routes by adding the middleware to the array of functions in a given routes file. The functions in the array will be executed in order to handle the route. For example:
```js
// app/routing/routers/index.routes.js
const index = {
prefix: '/',
middleware: [],
get: {
'/': [ _flitter.mw('HomeLogger'), _flitter.controller('Home').welcome ],
},
}
module.exports = exports = index
```
Here, the `HomeLogger` middleware will be applied to the `/` route before its handler is executed.
#### Creating Custom Middleware
Custom middleware files can be created and placed in the `app/routing/middleware/` folder. As long as they conform to the form required, they will be loaded by Flitter when the application is started, and they can be accessed by name via the built-in `_flitter.mw()` function. You can create a new middleware file from the template using a `./flitter` command:
`./flitter new middleware subdirectory:file_name`
Which will create the file `app/routing/routers/subdirectory/file_name.routes.js`:
```js
/*
* file_name Middleware
* -------------------------------------------------------------
* Put some description here!
*/
class file_name {
/*
* Run the middleware test.
* This method is required by all Flitter middleware.
* It should either call the next function in the stack,
* or it should handle the response accordingly.
*/
test(req, res, next){
console.log("Do stuff here!")
/*
* Call the next function in the stack.
*/
next()
}
}
module.exports = file_name
```
**Next: [3: Views & Static Assets](tutorial-Getting Started 3_ Views & Static Assets.html)**

30
doc/pages/Getting Started 3: Views & Static Assets.md

@ -0,0 +1,30 @@
Flitter uses Pug as a view engine, so, for the most part, working with views is a standard experience.
#### Creating Views
Creating views in Flitter is identical to any other Pug view. You can find more info on that [here.](https://pugjs.org/api/getting-started.html) Views should be placed in the `views/` directory and follow the Flitter standard convention for sub-directories, so `views/auth/login.pug` can be accessed as `auth:login`.
#### Serving Views
Flitter makes a global function `_flitter.view()` available for use in controller methods. To serve a view as a response, simple return the `_flitter.view()` function with the name of the view. **View names follow the standard Flitter convention for sub-directories.** Here's an example of a controller method that serves a view:
```js
// app/controllers/Auth.controller.js
class AuthController {
login(req, res){
return _flitter.view(res, 'auth:login', {destination: req.session.destination})
}
}
module.exports = AuthController
```
Here, the `login()` method returns the pug view located at `views/auth/login.pug`. The first argument is the response object. The second argument is the Flitter name of the view. The optional third argument is an object of variables to be passed to the view. The object keys correspond to the variable names in the template. That is `{ username: "foobar" }` can be accessed as `{{ username }}` in the template.
#### Static Assets
Flitter serves static assets from the `app/assets/` directory. All files in this folder are made publicly available using the `/assets` prefix. For example, the file `app/assets/flitter.png` can be accessed at `http://application.url/assets/flitter.png`. Flitter serves the `app/assets/favicon.ico` file specially using the [express-favicon](https://www.npmjs.com/package/express-favicon) plugin automatically. That means you can just replace the `app/assets/favicon.ico` file with a custom one for your app and Flitter will serve it automatically.
**Next: [4: Database & ORM](tutorial-Getting Started 4_ Database & ORM.html)**

76
doc/pages/Getting Started 4: Database & ORM.md

@ -0,0 +1,76 @@
Flitter uses [Mongoose](https://mongoosejs.com/) to provide MongoDB models. Model schemas are defined in files and loaded into Mongoose when Flitter initializes. MongoDB connection info is specified in the environment configuration.
#### Defining Model Schemata
> _Side Note:_
> **schemata** (n. pl.) - plural of schema
Model schemata define the structure and fields of the model. The schemata are passed directly to Mongoose, so for the most part, you can just reference the [Mongoose docs](https://mongoosejs.com/docs/guide.html) for how to create a schema. The schemata should be defined in individual files in the `app/models/` directory. Each of these files should return the object-definition of the Mongoose schema. Here's an example:
```js
// models/auth/User.js
const User = {
username: String,
password: String,
uuid: String,
}
module.exports = User
```
Custom model definitions can be created and added to the `app/models/` directory. You can use the ./flitter command to generate the model with any given name. **Model names are parsed from the file name and follow the standard Flitter convention for sub-directory naming.** For example:
`./flitter new model subdirectory:file_name`
This will generate the file `app/models/subdirectory/file_name.model.js`, which can be referenced as `subdirectory:file_name`:
```js
/*
* file_name Model
* -------------------------------------------------------------
* Put some description here!
*/
const file_name = {
field_name: String,
}
module.exports = exports = file_name
```
#### Using Models
When Flitter starts, it loads each of the schemata and creates Mongoose models. These models are made available globally via the `_flitter.model()` function. **Flitter model names are parsed from the file name and follow the standard Flitter convention for sub-directory naming.** Interacting with these models is simply interacting with normal Mongoose models, for which you can refer to the excellent [Mongoose docs](https://mongoosejs.com/docs/models.html). Here's an example of using the global function to use a model:
```js
// excerpt from app/controllers/Auth.controller.js
login_post( req, res ){
// Get the User model.
const User = _flitter.model('auth:User')
// Interact with it as you normally would.
User.findOne({username: req.body.username}, (err, user) => {
if ( !user ){
create_error_session('username', 'Invalid username.')
return res.redirect('/auth/login')
}
bcrypt.compare( req.body.password, user.password, (err, match) => {
if ( !match ){
create_error_session('password', 'Invalid password.')
return res.redirect('/auth/login')
}
else {
_flitter.controller('Auth').create_auth_session(req, user)
return res.redirect('/auth/dash')
}
})
})
}
```
**Next: [5: Controllers](tutorial-Getting Started 5_ Controllers.html)**

82
doc/pages/Getting Started 5: Controllers.md

@ -0,0 +1,82 @@
Controllers are the center of logic in Flitter. Routes should use controller methods to handle requests, and the controller methods should do all of the actual logic to process said requests. Controllers are defined in the `controllers/` directory, and are made available globally via the `_flitter.controller()` function.
#### Creating Controllers
Controllers are defined in the `app/controllers/` directory and have the `.controller.js` extension. **Controller names are parsed from the file names and follow the standard Flitter convention for sub-directory naming.** Controller classes are just normal classes with methods that take the Express request and response objects and should send a response back. Here's an example controller file:
```js
// app/controllers/Home.controller.js
class Home {
welcome(req, res){
return view(res, 'welcome')
}
error(req, res){
const error_string = "Uh, oh! Error."
return res.send(error_string)
}
}
module.exports = Home
```
Controller classes are allowed to contain whatever methods or class variables that you want. However, methods that will be used as handlers for routes should accept the Express request and response objects as arguments and should send a response at some point, then return the status of that sending to Flitter. The two main ways this can be done are directly, using the response object (see the [Express docs](https://expressjs.com/en/api.html#res)) or using the global `_flitter.view()` function.
New controller files can be created from Flitter's template using the `./flitter` command, like so:
`./flitter new controller subdirectory:file_name`
This command will create the file `app/controllers/subdirectory/file_name.controller.js` which can be referenced as `subdirectory:file_name`:
```js
/*
* file_name Controller
* -------------------------------------------------------------
* Put some description here!
*/
class file_name {
/*
* Serve the main page.
*/
main(req, res){
/*
* Return the main view.
* It must be passed the response.
* View parameters can be passed as an optional third
* argument to the view() method.
*/
return _flitter.view(res, 'view_name')
}
}
module.exports = exports = file_name
```
#### Using Controllers
Route files should not contain any actual logic. Instead, they should simply list middleware to be applied to a route, and then reference a method on a controller that should contain the logic for that route. Flitter makes available a global controller() function that returns instances of the defined controllers by name. **Controller names are based on the file name and follow the Flitter convention for sub-directory naming.** Here's an example:
```js
// app/routing/routes/index.routes.js
const index = {
prefix: '/',
middleware: [
_flitter.mw('HomeLogger'),
],
get: {
'/': [ _flitter.controller('Home').welcome ],
},
}
module.exports = index
```
Here, the `_flitter.controller('Home')` call gets an instance of the controller file `app/controllers/Home.controller.js` and tells Flitter to use the `welcome` method to handle the `/` route. It's important to note that the reference does _not_ call the actual function, but just references the name. Flitter will call the function when it receives a request.
That is, this is wrong: `_flitter.controller('Home').welcome()`
But this is correct: `_flitter.controller('Home').welcome`
**Next: [6: The ./flitter Command](tutorial-Getting Started 6_ The flitter Command.html)**

35
doc/pages/Getting Started 6: The flitter Command.md

@ -0,0 +1,35 @@
By default, Flitter ships with the [flitter-cli](https://git.glmdev.tech/flitter/cli) package, which enables use of the `./flitter` command. This command is designed to aid development on Flitter. This command should only be run from the root of the application, as it depends on relative paths.
#### Launch a Development Server
`./flitter up`
While not designed for use in production, `./flitter` can start a Flitter server quickly for testing your app.
#### Run a Deployment
`./flitter deploy <deployment name>`
Flitter units can define special functions called deployments. These deployments are designed to be one-time, non-reversible functions that set up the unit's necessary files and configuration, etc. For example, the [flitter-auth](https://git.glmdev.tech/flitter/auth) package provides the `auth` deployment which creates the controllers, models, views, and middleware to enable flitter-auth.
#### Create a New File From a Template
`./flitter new <template name> <file name>`
Flitter units can also define templates for files that are used regularly in Flitter. For example, [libflitter](https://git.glmdev.tech/flitter/libflitter) provides several templates like the `controller` template, which creates a new controller with the given `<file name>` in the base `controllers/` directory.
#### Launch the Flitter Shell
`./flitter shell`
Sometimes, when you're developing a feature for your app, it's useful to be able to test things out and interact with Flitter directly, without having to interact with Flitter through a web-browser. As such, this command will start an interactive prompt that has access to the entire Flitter context. It behaves just like an interactive Node prompt, but it is started from within Flitter, so you can access all the same resources that your app can directly.
#### Upgrade Flitter In-Place _(experimental)_
`./flitter flap`
Flitter Flap is an experimental migration tool designed to help keep Flitter applications up-to-date even after they're deployed. Once Flitter's core packages are updated via Yarn, you can use Flap to run any migrations those packages provide. These will make changes to the core Flitter file structure/syntax to keep your app up to date with the latest Flitter core. _This feature is relatively new and is a work in progress._
**Next: [7: Production](tutorial-Getting Started 7_ Production.html)**

23
doc/pages/Getting Started 7: Production.md

@ -0,0 +1,23 @@
While the `./flitter` provides a command to start the Flitter server, it's recommended that you use the `index.js` file to start the production server. This file will start the Flitter server and can be started by running:
`node index.js`
This is especially helpful for running Flitter as a service, like with `systemd`:
```bash
[Unit]
Description=MyApp - my app built on Flitter
Documentation=http://app.url/
After=network.target
[Service]
Type=simple
User=flitter-user
WorkingDirectory=/path/to/flitter
ExecStart=/usr/bin/env node /path/to/flitter/index.js
Restart=on-failure
[Install]
WantedBy=multi-user.target
```

26
doc/pages/Miscellaneous Info.md

@ -0,0 +1,26 @@
### Error Philosophy
When errors occur in Flitter, be they code or user, they should be handled and passed up to the next highest level so as to be as non-disruptive and transparent as possible. Errors should be handled as appropriate at the lowest level possible. For example, server errors that don't affect the user should not disrupt the request-response flow, but should be logged in the console. If a sub-process of a controller fails, an error should be shown to the user, but the request shouldn't fail to send.
### Bug Reports & Security Issues
Found a bug in Flitter? Please let me know! I'll try to get them fixed as soon as possible. You can file general bug reports as issues in the respective [Gitea repositories here](https://git.glmdev.tech/flitter/). If you're not sure which package to file your report under, just put it in the [main Flitter repository](https://git.glmdev.tech/flitter/flitter). Flitter is [libre](https://www.gnu.org/philosophy/floss-and-foss.en.html), so any contributions are welcome!
Security bugs should be reported to [security@glmdev.tech](mailto:security@glmdev.tech).
### Releases
Flitter's sub-components are [semantically versioned](https://semver.org/). This means that, beyond version 1.0.0, all releases of the same major release number are backwards-compatible. This documentation defines the public API of said components.
The main Flitter framework is developed with a different philosophy. Occasional versioned releases may be made, but to the best of my ability, the master branch of the Flitter repository should always have the most up-to-date working version of the framework. I always push a yarn.lock file with the framework that specifies the exact versions of the packages I was running.
### NPM? Yarn.
I prefer Yarn as opposed to the Node Package Manager. It's effectively the same, but it works more reliably for me, and has little niceties that I prefer. As such, I use Yarn when developing Flitter. That means that, while you can install Flitter's packages with NPM, you won't have the benefit of a [lock file](https://yarnpkg.com/lang/en/docs/yarn-lock/). If you install packages with Yarn, you'll get the exact versions I had when I pushed the working framework.
### Copyright & License
Flitter, and any sub-components developed by me are Copyright © 2019 Garrett Mills.
Flitter and the aforementioned sub-components are released under the terms of the MIT license. They are subject to all terms within that license, [available here.](https://choosealicense.com/licenses/mit/) However, the MIT license is very permissive, which means you can basically use Flitter however you want, with no warranty provided by me, nor can I be held liable for their employment.

16
doc/pages/Navigation.md

@ -0,0 +1,16 @@
## Navigation
![](https://static.glmdev.tech/flitter/flitter-big.png)
Thanks for RTFM. Flitter's documentation is generated using JSDoc. Here are a few helpful links.
- Getting Started with Flitter
- [0: Get Set Up](tutorial-Getting Started 0_ Get Set Up.html)
- [1: Configuration](tutorial-Getting Started 1_ Configuration.html)
- [2: Routing](tutorial-Getting Started 2_ Routing.html)
- [3: Views & Static Assets](tutorial-Getting Started 3_ Views & Static Assets.html)
- [4: Database & ORM](tutorial-Getting Started 4_ Database & ORM.html)
- [5: Controllers](tutorial-Getting Started 5_ Controllers.html)
- [6: The ./flitter Command](tutorial-Getting Started 6_ The flitter Command.html)
- [7: Production](tutorial-Getting Started 7_ Production.html)

55
doc/pages/Overview: flitter-agenda.md

@ -0,0 +1,55 @@
# Overview
flitter-agenda provides a wrapper for the [Agenda scheduler](https://github.com/agenda/agenda). This allows jobs to be defined and scheduled programmatically, and persistently. It uses the same MongoDB database as the rest of the Flitter application.
For the most part, using flitter-agenda is identical to using vanilla Agenda. The only main difference is that jobs should be defined in individual files in a given directory (by default, `app/jobs`). Each of these files should export a class that extends `flitter-agenda/Job`. The name of a job is parsed from its file name, recursively. flitter-agenda uses the Flitter convention for sub-directory naming, meaning that sub-directories are delineated with a `:` character. For example, the job named `auth:CleanUsers` would be located in the file `app/jobs/auth/CleanUsers.job.js`.
#### Installation
flitter-agenda doesn't ship with Flitter by default, but it's pretty easy to add. First, install the package:
`yarn add flitter-agenda`
Now, modify the `Units.flitter.js` file. Add the add the following line to the "Custom Flitter Units" section to tell Flitter to load flitter-agenda:
```javascript
'Agenda' : new (require('flitter-agenda/AgendaUnit'))(),
```
Et voilà! You should now have access to flitter-agenda.
#### Creating a Sample Job
As an example, we'll create a sample job that prints a some text to the console.
First, create a new job definition file. We can do this using the built in template with the `./flitter` command:
`./flitter new job Log`
This will create the file `app/jobs/Log.job.js`. Open it up, and define the actual logic for the job:
```javascript
const Job = require('flitter-agenda/Job')
class LogJob extends Job {
exec(job, done){
const {msg} = job.attrs.data
_flitter.log(msg)
done()
}
}
module.exports = exports = LogJob
```
Here, we take the job argument `msg` and pass it to Flitter's `log()` function.
We can test out our job by running it from the Flitter shell. Here's a sample output:
```javascript
(flitter)> _flitter.scheduler.schedule('in 3 seconds', 'Log', {msg: "This is a message!"})
Promise { <pending> }
(flitter)> This is a message!
```
`_flitter.scheduler` is the running instance of Agenda, so you can call any function on it like you normally would such as `schedule()`, `every()`, even `define()`. ([See the Agenda docs.](https://github.com/agenda/agenda)) Here, we scheduled the `Log` job we created to run 3 seconds after the `schedule()` function is called, and passed it a message.

12
doc/pages/Overview: flitter-auth.md

@ -0,0 +1,12 @@
flitter-auth provides a basic user-authentication framework for Flitter. When deployed, it creates controllers, middleware, routes, and views to add login/registration/session/route-protection functionality to your application. The sample views that ship with flitter-auth provide a login page, registration page, very basic dashboard, and error page.
flitter-auth creates a basic User model that stores the username, UUID, hashed password, and a misc. JSON data array for each user. When a user logs in, this object (sans password) is stored in the session so it can be used when handling requests. When a user logs out, this object is removed. Passwords are encrypted using [bcrypt](https://www.npmjs.com/package/bcrypt), and their hashes are stored.
#### Quick Start
By default, Flitter ships with flitter-auth available. To set it up, just run the deployment:
`./flitter deploy auth`
Now, you should be able to access the `/auth/register`, `/auth/login`, and `/auth/dash` routes. flitter-auth makes its files available in the app-space so they can be customized and extended to work with your app better.

29
doc/pages/Overview: flitter-cli.md

@ -0,0 +1,29 @@
flitter-cli provides a number of CLI tools for Flitter with the goal of making the Flitter development flow easier. In Flitter, this functionality is made available in the form of the ./flitter command.
This command exposes an endpoint for the flitter-cli logic, and is usually formatted like so:
```js
#!/usr/bin/env node
/*
* ./flitter
* -------------------------------------------------------------
* The ./flitter command is used to interact with Flitter and its tools
* in the development environment. Currently, it lends access to Flitter
* shell, which is like a Node interactive prompt, but it's launched from
* within the same context as the Flitter HTTP server, allowing developers
* to interact with Flitter directly.
*/
const CliAppUnit = require('flitter-cli/CliAppUnit')
const units = require('./Units.flitter')
units['App'] = new CliAppUnit()
const FlitterApp = require('libflitter/app/FlitterApp')
const flitter = new FlitterApp(units)
flitter.up()
```
This expects the file to be called with command line inputs, which flitter-cli processes then handles.

114
doc/pages/Overview: flitter-forms.md

@ -0,0 +1,114 @@
flitter-forms provides form-template validation and session-based error messages for Flitter. This means that validator schemata are defined for each form, then they are applied to a request submitting information from the corresponding form. That request body is validated using the requirements specified in the form's schema. Any validation errors are written to the session and the user is returned to the form. When the validation completes, the request is passed to the route handler along with the input fields specified in the schema.
### Getting Started
We'll take a look at how to create and validate a sample registration form using flitter-forms by defining a validation schema, creating a very basic view, and a handler for it.
#### Define Validation Schema
Validation schemata contain all fields you wish to access from the form. A validation schema is an `Object` of key-value pairs where the keys are the names of the fields as they will be submitted and the values are each an `Array`. Each `Array` should be a collection of validators that will be applied, in order, to that form.
Let's create a new schema from a template using `./flitter`:
`./flitter new validator RegistrationForm`
This will create the file `app/validators/RegistrationForm.validator.js`. We will edit it to define the fields in our form and the validator criteria that should be applied to each of those fields:
```js
// RegistrationForm Validator
const RegistrationForm = {
// field name validators
'username': [ 'required', 'isAlphanumeric' ],
'password': [ 'required', 'verify', 'isLength:{"min":8}' ]
}
module.exports = exports = RegistrationForm
```
flitter-forms makes available a superset of criteria containing those from the [validator](https://www.npmjs.com/package/validator#validators) package. The name of the criterion should be specified as a `String`. Any arguments that the criterion takes can be specified as a `String` of valid JSON separated from the criterion name by a `:` character. [More info on available criteria here.](https://kb.glmdev.tech/books/flitter/page/the-validation-criteria)
#### Create the Form View
Now, we'll create a very basic form in the `views/register.pug` file:
```pug
html
head
title Register
body
for field in form.errors
for error in field
p(style='color: red;') Error: #{error}
form(action='/register' method='POST' enctype='multipart/form-data')
input(name='username' placeholder='Username' required)
br
input(name='password' placeholder='Password' required)
br
input(name='password_verify' placeholder='Verify Password' required)
br
button(type='submit') Submit
```
Note that `password_verify` is required because we specified the `verify` criterion for the `password` field.
#### Define the Routes
Now, we will add routes for our form to `app/routing/routers/index.routes.js`:
```js
const index = {
prefix: '/',
get: {
'/': [ _flitter.controller('Home').welcome ],
'/register': [ _flitter.controller('Home').show_registration ]
},
post: {
'/register': [ _flitter.controller('Home').handle_registration ]
},
}
module.exports = index
```
Here we specified that the `GET` request should be handled by the `show_registration` method on the `Home` controller, and the `POST` request should be handled by the `handle_registration` method on the `Home` controller.
#### Handle the Requests
We need to add these methods to the `Home` controller (`app/controllers/Home.controller.js`). First, add the `show_registration` method:
```js
show_registration(req, res){
// initialize the form in the session
const form =_flitter.validator('RegistrationForm')
_flitter.init_form(form, req)
return _flitter.view(res, 'register', {form: req.session.forms.RegistrationForm})
}
```
Here, we initialize the form in session. If the form already has data in the session, they will not be destroyed. Then, we render the registration page and pass it the form object as `form`.
Then, add the `handle_registration` method (this is just a dummy method):
```js
handle_registration(req, res){
const validator = _flit.validator('RegistrationForm')
// Tell the validator to handle the request.
validator.handle(req, res, '/register', (req, res, input) => {
// The validation was successful! Let's just look at the input.
res.send(JSON.stringify(input))
})
}
```
Forms makes validators available by name through the `_flitter.validator()` function, so we get the `RegistrationForm` validator. Then, we tell the validator to handle the request. If the validation fails, the user will be redirected to the `/register` route and the error will be displayed. If it is successful, then the callback function will be executed, responding with the `input` data as JSON to the user (as a demo).

34
doc/pages/Overview: flitter-less.md

@ -0,0 +1,34 @@
# Overview
flitter-less is an in-place compiler for [LESS style-sheets](http://lesscss.org/) for Flitter. That is, LESS styles placed in `app/assets/less/` can be accessed as compiled CSS directly by referencing the name of the style-sheet after the `/style-asset` route.
#### Installation
Install the flitter-less package:
`yarn install flitter-less`
Add the Unit to the `Units.flitter.js` file, in the "Custom Units" section:
```javascript
const FlitterUnits = {
// ... other units ...
/*
* Custom Flitter Units
* -------------------------------------------------------------
* Custom units should be specified here. They will be loaded in order
* after the core of Flitter has been initialized.
*/
'Less' : new (require('flitter-less/LessUnit'))(),
// ... other units ...
}
module.exports = exports = FlitterUnits
```
#### Use
Using flitter-less is pretty straightforward. Place your `.less` files in `app/assets/less/`. Now, you can access them at the `/style-asset` route. For example, `app/assets/less/index.less` can be accessed at `/style-asset/index.css`.

101
doc/pages/Overview: flitter-upload.md

@ -0,0 +1,101 @@
# Overview
flitter-upload provides a simple, middleware-based file upload system for Flitter. This allows files to be uploaded, stored, and categorized seamlessly. This is done with the hope of reducing the hassle of keeping track of and serving uploaded files. Here, we'll build a sample picture uploader to take a look at how to use flitter-upload.
#### Step 0: Create the basic upload form and routes.
Create `views/upload.pug`:
```pug
html
body
form(method='post' enctype='multipart/form-data')
input(type='file' name='img' required)
button(type='submit') Upload
```
Create the route in `app/routing/routers/index.routes.js`:
```javascript
get: {
'/': [ _flitter.controller('Home').welcome ],
'/upload': [ _flitter.controller('Home').get_upload ],
},
```
Create the controller function in `app/controllers/Home.controller.js`:
```javascript
get_upload(req, res){
return _flitter.view(res, 'upload')
}
```
#### Step 1: Create the upload submission route.
Create the route in `app/routing/routers/index.routes.js`:
```javascript
post: {
'/upload': [ _flitter.upload('img', 'upload-img'), _flitter.controller('Home').post_upload ]
},
```
Here, we invoke the `**_flitter**.upload()` middleware before we pass off control to the `post_upload()` handler. We call this middleware with two arguments. The first is the name of the file uploaded. In this case, `img`. It should match exactly the `name` attribute of the file input field. The second is an arbitrary string that categorizes the uploaded files. In our case, they will be tagged `upload-img`. The files are then stored in the specified upload directory, `uploads/` by default.
#### Step 2: Add the controller method.
Add the following to `app/controllers/Home.controller.js`:
```js
post_upload(req, res){
const file_name = req.files.img.new_name
res.send('File uploaded as: '+file_name)
}
```
The `**_flitter**.upload()` middleware replaces whatever file name it uploads with an instance of `flitter-upload/deploy/File` in `req.files`. Here, that instance is created in `req.files.img`. Then, we just send a text response to the user with the UUID name of the file.
#### Step 3: Add the retrieval route.
Create the route in `app/routing/routers/index.routes.js`:
```js
get: {
'/': [ _flitter.controller('Home').welcome ],
'/upload': [ _flitter.controller('Home').get_upload ],
'/file/:name' : [ _flitter.controller('Home').get_file ],
},
```
Add the handler function in `app/controllers/Home.controller.js`:
```js
get_file(req, res, next){
// Get the name of the file from the route.
const file_name = req.params.name
// Find the file instance with the corresponding name.
_flitter.model('upload:File').findOne({new_name: file_name}, (error, file) => {
// If an error occurs, let Flitter handle it.
if ( error ) next(error)
// If the file isn't found, throw a 404 error.
if ( file === null ) {
const e = new Error('File with name not found.')
e.status = 404.
next(e)
}
else {
// Otherwise, serve the file.
res.type(file.mime)
res.sendFile(path.resolve(_flitter.upload_destination, file.new_name))
}
})
}
```
Now the file with the given name will be served to the user. Here, we find the instance of the `flitter-upload/deploy/File` model with the given file name. If the file is found, serve it to the user. flitter-upload handles the MIME type setting.

23
doc/pages/Overview: libflitter.md

@ -0,0 +1,23 @@
# Overview
libflitter is the core package that runs Flitter. Flitter's initialization structure is broken into individual bits called units, and these units are chained one after the other to create the initialization chain (usually referred to as the stack or the chain).
Each unit contains a function that, when called, initializes that particular unit. When forming the stack, Flitter passes each unit's function to the one before it. When it finishes initializing itself, the unit should call that function from within whatever scope it initializes. In Flitter, these are called **contexts**. The concept of contexts is important to understanding how Flitter works. Each unit of the initialization chain creates a context and the rest of the chain continues from within that context, and therefore has access to the resources that context exposes.
For example, one of the first units to start is the `UtilityUnit`. This unit creates a helper function, `log()`, which is bound to `UtilityUnit`'s context. From that point on, all other units that initialize have access to this function via the `utility` context.
Another example is the `DatabaseUnit`. The database unit reads the configured model schemata and creates Mongoose models from them. Then, it uses Mongoose to connect to the configured MongoDB connection. It waits for the database to connect before returning the function. That means that all other units loaded after that are guaranteed access to the database -- they run from within (a.k.a. under) the database context.
`libflitter` contains the units that comprise the core functionality of Flitter, as well as the base classes for things like the `FlitterApp` and `Unit` files. In order, `libflitter` provides the following units:
- `ConfigUnit` - parses config files and makes their values available
- `UtilityUnit` - various utility functions and info
- `ExpressUnit` - configures the underlying Express application
- `ViewEngineUnit` - configures the Pug view engine and helper functions
- `DatabaseUnit` - creates the database models and connects to MongoDB
- `MiddlewareUnit` - loads middleware definitions and makes them available
- `RoutingUnit` - register the route definitions with the underlying Express app
- `StaticUnit` - make files in the `assets/` directory available on the `/assets` route
- `ErrorUnit` - create last-resort handlers for HTTP errors
- `AppUnit` - launch the Express HTTP server

15
generate

@ -0,0 +1,15 @@
#!/bin/bash
REPOS=( libflitter cli auth forms upload agenda flap less )
mkdir clone
for i in "${REPOS[@]}"
do
git clone https://git.glmdev.tech/flitter/$i clone/$i
done
./node_modules/.bin/jsdoc --configure ./jsdoc.json --verbose &&
rm -rf clone &&
echo "Generated Flitter documentation site files: $(pwd)/out"

32
jsdoc.json

@ -0,0 +1,32 @@
{
"tags": {
"allowUnknownTags": true,
"dictionaries": ["jsdoc"]
},
"source": {
"include": ["clone"],
"excludePattern": "(node_modules/|docs)"
},
"plugins": [
"plugins/markdown"
],
"templates": {
"referenceTitle": "Flitter",
"disableSort": false,
"collapse": true,
"resources": {
"Source Code": "https://git.glmdev.tech/flitter",
"Old Documentation": "https://kb.glmdev.tech/books/flitter",
"Garrett Mills": "https://glmdev.tech/"
}
},
"opts": {
"destination": "./out/",
"encoding": "utf8",
"private": true,
"recurse": true,
"tutorials": "./doc/pages",
"template": "./node_modules/docdash",
"readme": "./doc/README.md"
}
}

13
package.json

@ -0,0 +1,13 @@
{
"name": "fdoc",
"version": "0.1.0",
"description": "Documentation generator script and tutorials for Flitter.",
"main": "index.js",
"repository": "https://git.glmdev.tech/flitter/docs",
"author": "glmdev <garrett@glmdev.tech>",
"license": "MIT",
"dependencies": {
"docdash": "^1.1.0",
"jsdoc": "^3.5.5"
}
}

123
yarn.lock

@ -0,0 +1,123 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
babylon@7.0.0-beta.19:
version "7.0.0-beta.19"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.19.tgz#e928c7e807e970e0536b078ab3e0c48f9e052503"
integrity sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==
bluebird@~3.5.0:
version "3.5.4"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.4.tgz#d6cc661595de30d5b3af5fcedd3c0b3ef6ec5714"
integrity sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==
catharsis@~0.8.9:
version "0.8.9"
resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.8.9.tgz#98cc890ca652dd2ef0e70b37925310ff9e90fc8b"
integrity sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=
dependencies:
underscore-contrib "~0.3.0"
docdash@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/docdash/-/docdash-1.1.0.tgz#ffa57f9d227c3b11d95ca69807ed874281316b6b"
integrity sha512-sgcessH25PU6CIoJGeOjDCv9CLi6Z6TLwc553s/O+DKCnIRXT58e4CG3WsgI8Zea5ZQcjsfa8u4IwKqR2TKTxQ==
escape-string-regexp@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
graceful-fs@^4.1.9:
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
js2xmlparser@~3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-3.0.0.tgz#3fb60eaa089c5440f9319f51760ccd07e2499733"
integrity sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=
dependencies:
xmlcreate "^1.0.1"
jsdoc@^3.5.5:
version "3.5.5"
resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.5.5.tgz#484521b126e81904d632ff83ec9aaa096708fa4d"
integrity sha512-6PxB65TAU4WO0Wzyr/4/YhlGovXl0EVYfpKbpSroSj0qBxT4/xod/l40Opkm38dRHRdQgdeY836M0uVnJQG7kg==
dependencies:
babylon "7.0.0-beta.19"
bluebird "~3.5.0"
catharsis "~0.8.9"
escape-string-regexp "~1.0.5"
js2xmlparser "~3.0.0"
klaw "~2.0.0"
marked "~0.3.6"
mkdirp "~0.5.1"
requizzle "~0.2.1"
strip-json-comments "~2.0.1"
taffydb "2.6.2"
underscore "~1.8.3"
klaw@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/klaw/-/klaw-2.0.0.tgz#59c128e0dc5ce410201151194eeb9cbf858650f6"
integrity sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=
dependencies:
graceful-fs "^4.1.9"
marked@~0.3.6:
version "0.3.19"
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==
minimist@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
mkdirp@~0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
dependencies:
minimist "0.0.8"
requizzle@~0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/requizzle/-/requizzle-0.2.1.tgz#6943c3530c4d9a7e46f1cddd51c158fc670cdbde"
integrity sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=
dependencies:
underscore "~1.6.0"
strip-json-comments@~2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
taffydb@2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268"
integrity sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=
underscore-contrib@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/underscore-contrib/-/underscore-contrib-0.3.0.tgz#665b66c24783f8fa2b18c9f8cbb0e2c7d48c26c7"
integrity sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc=
dependencies:
underscore "1.6.0"
underscore@1.6.0, underscore@~1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
integrity sha1-izixDKze9jM3uLJOT/htRa6lKag=
underscore@~1.8.3:
version "1.8.3"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=
xmlcreate@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-1.0.2.tgz#fa6bf762a60a413fb3dd8f4b03c5b269238d308f"
integrity sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=
Loading…
Cancel
Save