# Yekonga Server

[![oclif](https://img.shields.io/badge/cli-oclif-brightgreen.svg)](https://oclif.io)
[![Version](https://img.shields.io/npm/v/yekonga-server.svg)](https://npmjs.org/package/yekonga-server)
[![Downloads/week](https://img.shields.io/npm/dw/yekonga-server.svg)](https://npmjs.org/package/yekonga-server)
[![License](https://img.shields.io/npm/l/yekonga-server.svg)](https://github.com/https://github.com/robertkonga/yekonga-server/yekonga-server/blob/master/package.json)

Yekonga Server is an open source backend that can be deployed to any infrastructure that can run Node.js.
## Description
Yekonga Server works with the Express web application framework. It can be added to existing web applications, or run by itself.

## Contents
- [Getting Started](#getting-started)
    - [Installing](#installing)
    - [Create server](#Create-server)
    - [Running Yekonga Server](#running-yekonga-server)
- [Database Structure](#database-structure)
- [Configuration](#configuration)
    - [Configuration value can be accessed as below](#configuration-value-can-be-accessed-as-below)
    - [Configuration is the json file with below data](#configuration-is-the-json-file-with-below-data)
    - [Permissions configuration](#permissions-configuration)
    - [Graphql configuration](#graphql-configuration)
    - [Graphql.authQuery configuration](#graphql-authQuery-configuration)
    - [Database configuration](#database-configuration)
    - [Database.{key} configuration](#databasekey-configuration)
    - [Authentication configuration](#authentication-configuration)
    - [Ports configuration](#ports-configuration)
    - [Mail configuration](#mail-configuration)
    - [Mail.smtp configuration](#mailsmtp-configuration)
- [Database Function](#database-function)
    - [DataModel Functions](#datamodel-functions)
    - [Database Functions](#database-functions)
- [Cloud Functions](#cloud-functions)
    - [Define Functions](#define-functions)
    - [Trigger Functions](#trigger-functions)
- [Custom url](#custom-url)
- [Helper functions](#helper-functions)
- [Graphql](#graphql)
    - [Custom Action](#custom-action)
    - [Custom Graphql](#custom-graphql)
- [Author](#author)
- [Version History](#version-history)
- [License](#license)


## Getting Started
### Installing
you can install globelly
```
npm install -g yekonga-server 
```
OR direct to your project

```
npm install --save yekonga-server
```

### Create server

```sh-session

VERSION
  yekonga-server/2.1.7 win32-x64 node-v14.15.4

USAGE
  $ yekonga [COMMAND]

COMMANDS
  create    Describe the command here
  generate  Describe the command here
  help      display help for yekonga
  
```
### Create project command
```sh-session
USAGE
  $ yekonga create [PROJECT]

OPTIONS
  -d, --dirname=dirname  app directory name
  -f, --force            Force generate / overide existing
  -h, --help             show CLI help
  -n, --name=name        app name
  -p, --port=port        [default: 1093] server running port
  -p, --public=public    [default: public] generate public directory
  -t, --triggers         create triggers function
  -t, --template         generate template
  -v, --version          show CLI version
```
### Generate triggers command
```sh-session

USAGE
  $ yekonga generate

OPTIONS
  -c, --config=config      App config file name
  -d, --database=database  App database structure file name
  -f, --force              Force generate / overide existing
  -h, --help               show CLI help
  -t, --trigger=trigger    Trigger name
  -t, --triggers           Force generate / overide existing
  -v, --version            show CLI version

```

### Running Yekonga Server

```js
require('yekonga-server');
// configation json file
const config = require('./config.json');

// database structure json file
const database = require('./database.json');

// set configarations
Yekonga.setConfig(config, database); 

// start the server
Yekonga.startServer();  
```

## Database structure
database structure is json array of objects, Database structure sample json file with `"users"` collection
```json
[
    {
        "_id": { "collection": "users" },
        "userId": { "type": "String", "default": null, "required": true },
        "firstName": { "type": "String", "default": null, "required": true },
        "lastName": { "type": "String", "default": null, "required": true },
        "email": { "type": "String", "default": null, "required": true },
        "password": { "type": "String", "default": null, "required": true },
        "role": { "type": "String", "default": null, "required": true },
        "token": { "type": "String", "default": null, "required": false },
        "status": { "type": "Number", "default": 0, "required": true },
        "isActive": { "type": "Boolean", "default": false, "required": true },
        "createdAt": { "type": "Date", "default": "now", "required": false },
        "updatedAt": { "type": "Date", "default": "now", "required": false }
    },
]
```
Take one field of users collection
```json
"userId": { "type": "String", "default": null, "required": true },
```

* ```"userId"``` is the field of the collection/table
* ```"type"``` => ```userId.type``` is data type to be stored
* ```"default"``` => ```userId.default``` is default value
* ```"required"``` => ```userId.required``` werether the value required or not

Database structure can be accessed via
```js
Yekonga.Schema
```


## Configuration
#### Configuration value can be accessed as below
```js
// config value can be access by 
Yekonga.Config.{key}

// example
Yekonga.Config.appName
Yekonga.Config.ports.server
```
#### Configuration is the json file with below data

| Key | Default Value | Type | | Description |
|-|-|-|-|-|
| `appName`  | System | String | Option | Name of the application |   
| `appId`  | null | String | Option | Application ID |  
| `masterKey`  | null | String | Option | application key |   
| `enableAppKey`  | false | boolean | Option | Make api access require   appId or not |       
| `domain`  | null | string | Option | main domain that allow to access app |   
| `domainAlias`  | [] | array | Option | all alowed domains | 
| `address`  | System | String | Option | host IP address  
| `baseUrl`  | System | String | Option | - |
| `restApi`  | System | String | Option | - |
| `restAuthApi`  | System | String | Option | - |
| `secureOnly`  | System | String | Option | - |
| `debug`  | System | String | Option | - |
| `endToEndEncryption`  | System | String | Option | - |
| `authPlaygroundEnable`  | System | String | Option | - |
| `apiPlaygroundEnable`  | System | String | Option | - |
| `enableDashboard`  | false | boolean | Option | - |
| `allowCreateFrontend`  | false | boolean | Option | - |
| `namingConvection`  | camelcase | String | Option | `camelcase`, `underscorecase`|
| `columnNamingConvection`  | underscorecase | String | Option | - |
| `namingConvectionOptions`  | ["camelcase", "underscorecase"] | array | Option | - |
| `public`  | public | String | Option | - |
| `cloud`  | null | String | Option | - |
| `logFile`  | null | String | Option | - |
| `emailTemplate`  | null | String | Option | - |
| `googleApiKey`  | null | String | Option | - |
| `permissions`  | [more...](#permissions-configuration) | object | Option | - |
| `graphql`  | [more...](#graphql-configuration) | object | Option | - |
| `defaultDatabase`  | mongoDB | String | Option | - | | 
| `database`  | [more...](#database-configuration) | object | required | - |
| `authentication`  | [more...](#authentication-configuration) | object | Option | - |
| `ports`  | [more...](#ports-configuration) | object | Option | - |
| `mail`  | [more...](#mail-configuration) | object | Option | - |

#### Permissions configuration
| Key | Default Value | Type | | Description |
|-|-|-|-|-|
| `authActions`  | [] | array | Option | - |
| `guestActions`  | [] | array | Option | - |   

#### Graphql configuration
| Key | Default Value | Type | | Description |
|-|-|-|-|-|
| `apiRoute`  | null | string | Option | graphql api route |
| `apiAuthRoute`  | null | string | Option | graphql api route |
| `customTypes`  | null | string | Option | relative path of custom graphql schema  |
| `customResolvers`  | null | string | Option | relative path of custom graphql resolver |
| `customAuthTypes`  | null | string | Option | relative path of auth custom graphql schema  |
| `customAuthResolvers`  | null | string | Option | relative path of auth custom graphql resolver |
| `enabledForClasses`  | [] | array | Option | - |   
| `disabledForClasses`  | [] | array | Option | - |   
| `authResolvers`  | [] | array | Option | - |   
| `authClasses`  | [] | array | Option | - |   
| `guestResolvers`  | [] | array | Option | - |   
| `guestClasses`  | [] | array | Option | - |   
| `authQuery`  | [more...](#graphql.authQuery-onfiguration) | string | Option | - |   

#### Graphql.authQuery configuration
| Key | Default Value | Type | | Description |
|-|-|-|-|-|
| `user`  | [] | array | Option | field that are to be queried on users
| `account`  | [] | array | Option | field that are to be queried on accounts

#### Database configuration
| Key | Default Value | Type | | Description |
|-|-|-|-|-|
| `{key}`  | [more...](#Database.{key}-configuration) | object | Option | -  |

#### Database.{key} configuration
| Key | Default Value | Type | | Description |
|-|-|-|-|-|
| `type`  | null | string | Option | - |
| `host`  | null | string | Option | - |
| `port`  | null | string | Option | - |
| `databaseName`  | null | string | Option | - |
| `username`  | null | string | Option | - | 
| `password`  | null | string | Option | - |
| `prefix`  | null | string | Option | - |
| `generateID`  | null | string | Option | - |
| `generateIDLengh`  | null | string | Option | - |

#### Authentication configuration
| Key | Default Value | Type | | Description |
|-|-|-|-|-|
| `saltRound`  | 7 | number | Option | - |
| `algorithm`  | HS512 | string | Option | - |
| `tokenSecret`  | null | string | Option | - |
| `cryptojsKey`  | null | string | Option | - |
| `cryptojsIv`  | null | string | Option | - |

#### Ports configuration
| Key | Default Value | Type | | Description |
|-|-|-|-|-|
| `server`  | null | number | Option | - |
| `secure`  | null | number | Option | - |
| `socket`  | null | number | Option | - |
| `redis`  | null | number | Option | - |

#### Mail configuration
| Key | Default Value | Type | | Description |
|-|-|-|-|-|
| `smtp`  | [more...](#Mail.smtp-configuration) | object | Option | - |

#### Mail.smtp configuration
| Key | Default Value | Type | | Description |
|-|-|-|-|-|
| `service`  | null | string | Option | - |
| `host`  | null | string | Option | - |
| `port`  | null | number | Option | - |
| `secure`  | null | boolean | Option | - |
| `from`  | null | string | Option | - |
| `domain`  | null | string | Option | - |
| `username`  | null | string | Option | - |
| `password`  | null | string | Option | - |

## Database Function

### DataModel Functions
> All DataModel method return promise

```js
Yekonga.DataModel.{Class}.{method}(args);
```

| Method | Params | Result | Description |
|-|-|-|-|
| `findOne` | `filter`, `context`, `isAdmin` | object | return object of Model class eg `User` |
| `find` | `filter`, `context`, `isAdmin` | array | return array of object of Model class eg `User` |
| `paginate` | `filter`, `context`, `isAdmin` | object | return object of Model class eg `User` |
| `download` | `filter`, `context`, `isAdmin` | object | return object of Model class eg `User` |
| `summary` | `filter`, `context`, `isAdmin` | object | return object of Model class eg `User` |
| `count` | `filter`, `context`, `isAdmin` | object | return object of Model class eg `User` |
| `sum` | `filter`, `context`, `isAdmin` | object | return object of Model class eg `User` |
| `max` | `filter`, `context`, `isAdmin` | object | return object of Model class eg `User` |
| `min` | `filter`, `context`, `isAdmin` | object | return object of Model class eg `User` |
| `graph` | `filter`, `context`, `isAdmin` | object | return object of Model class eg `User` |
| `create` | `filter`, `context`, `isAdmin` | object | return object of Model class eg `User` |
| `update` | `filter`, `context`, `isAdmin` | object | return object of Model class eg `User` |
| `delete` | `filter`, `context`, `isAdmin` | object | return object of Model class eg `User` |
#### Examples
```js
let user = await Yekonga.DataModel.User.findOne({}, null, true);
```
### Database Functions
#### Examples
```js
// single user object
let users = Yekonga.DB.table('users')
    .where('userId', 'xxx')
    .findOne();

// list of users
let users = Yekonga.DB.table('users').find();
```
## Cloud Functions

### Define Functions

Define function
```js
Yekonga.Cloud.define('someFunction', async function(data) {

});
```

Run defined function
```js
Yekonga.Cloud.run('someFunction', data);
```

### Trigger Functions 

Before Login
```js
Yekonga.Cloud.beforeLogin(async (data) => { /* code goes here */ });
```
After Login
```js
Yekonga.Cloud.afterLogin(async (data) => { /* code goes here */ });
```
Before OTP
```js
Yekonga.Cloud.beforeOtp(async (data) => { /* code goes here */ });
```
After OTP
```js
Yekonga.Cloud.afterOtp(async (data) => { /* code goes here */ });
```
Before Registration
```js
Yekonga.Cloud.beforeRegistration(async (data) => { /* code goes here */ });
```
After Registration
```js
Yekonga.Cloud.afterRegistration(async (data) => { /* code goes here */ });
```
Before Reset Password
```js
Yekonga.Cloud.beforeResetPassword(async (data) => { /* code goes here */ });
```
After Reset Password
```js
Yekonga.Cloud.afterResetPassword(async (data) => { /* code goes here */ });
```
Before Change Password
```js
Yekonga.Cloud.beforeChangePassword(async (data) => { /* code goes here */ });
```
After Change Password
```js
Yekonga.Cloud.afterChangePassword(async (data) => { /* code goes here */ });
```
Before Logout
```js
Yekonga.Cloud.beforeLogout(async (data) => { /* code goes here */ });
```
After Logout
```js
Yekonga.Cloud.afterLogout(async (data) => { /* code goes here */ });
```
Before Find
```js
Yekonga.Cloud.beforeFind('User', async (filter, context) => { /* code goes here */ });
```
After Find
```js
Yekonga.Cloud.afterFind('User', async (filter, context) => { /* code goes here */ });
```
Before Save
```js
Yekonga.Cloud.beforeSave('User', async (input, filter, context) => { /* code goes here */ });
```
After Save
```js

Yekonga.Cloud.afterSave('User', async (result, input, context) => { /* code goes here */ });
```
Before Create
```js

Yekonga.Cloud.beforeCreate('User', async (input, filter, context) => { /* code goes here */ });
```
After Create
```js

Yekonga.Cloud.afterCreate('User', async (result, input, context) => { /* code goes here */ });
```
Before Update
```js
Yekonga.Cloud.beforeUpdate('User', async (input, filter, context) => { /* code goes here */ });
```
After Update
```js

Yekonga.Cloud.afterUpdate('User', async (result, input, context) => { /* code goes here */ });
```
Before Delete
```js

Yekonga.Cloud.beforeDelete('User', async (filter, context) => { /* code goes here */ });
```
After Delete
```js

Yekonga.Cloud.afterDelete('User', async (result, context) => { /* code goes here */ });
```

### Custom url
```js
const app = Yekonga.route;

app.get('/custom-url', function(req, res){

    return res.send('This is custom url');
});
```

### Helper functions

| Method | Params | Result | Description |
|-|-|-|-|
| Yekonga.Helper.execAsync | (cmd, pipe, callback) | String | - | | String | - |
| Yekonga.Helper.encrypt | (data) | String | - |
| Yekonga.Helper.decrypt | (data) | String | - |
| Yekonga.Helper.bcrypt | (value) | String | - |
| Yekonga.Helper.formatPhone | (value) | String | - |
| Yekonga.Helper.get, async (url, headers) | String | - |
| Yekonga.Helper.post, async (url, body, headers, multipart) | String | - |
| Yekonga.Helper.request | (method, options) | String | - |
| Yekonga.Helper.saveFile | (data, dir) | String | - |
| Yekonga.Helper.generateFile | (template, options) | String | - |
| Yekonga.Helper.createFile | (file, data, isRoot) | String | - |
| Yekonga.Helper.writeFile | (file, data, isRoot) | String | - |
| Yekonga.Helper.readFile | (file) | String | - |
| Yekonga.Helper.copy | (source, dist, root) | String | - |
| Yekonga.Helper.rootPath | (filename) | String | - |
| Yekonga.Helper.realpath | (filename) | String | - |
| Yekonga.Helper.setLocation | (location) | String | - |
| Yekonga.Helper.checkLocation | (name, tag, parent) | String | - |
| Yekonga.Helper.storeFile | (req, res) | String | - |
| Yekonga.Helper.setLocalAddress, async (content) | String | - |
| Yekonga.Helper.getSystemInfo | () | String | - |
| Yekonga.Helper.textTemplate | (templateString, data) | String | - |
| Yekonga.Helper.uuid | () | String | - |
| Yekonga.Helper.uuid3 | () | String | - |
| Yekonga.Helper.uuid4 | () | String | - |
| Yekonga.Helper.uuid5 | () | String | - |
| Yekonga.Helper.uniqueId | (table) | String | - |
| Yekonga.Helper.getRandomString | (length, type) | String | - | 
| Yekonga.Helper.getRandomInt | (length) | String | - |
| Yekonga.Helper.getHexString | (length) | String | - |
| Yekonga.Helper.databaseUUID | () | String | - |
| Yekonga.Helper.getTitle | (value) | String | - |
| Yekonga.Helper.getHeading | (value) | String | - |
| Yekonga.Helper.getSentence | (value) | String | - |
| Yekonga.Helper.getSlug | (value) | String | - |
| Yekonga.Helper.getLink | (value) | String | - |
| Yekonga.Helper.getName | (value) | String | - |
| Yekonga.Helper.getTable | (value) | String | - |
| Yekonga.Helper.getClass | (value) | String | - |
| Yekonga.Helper.getClassVariable | (value, singular) | String | - |
| Yekonga.Helper.getVariable | (value) | String | - |
| Yekonga.Helper.getColumn | (value) | String | - |
| Yekonga.Helper.formatToVariables | (value) | String | - |
| Yekonga.Helper.formatToColumn | (value) | String | - |
| Yekonga.Helper.getDefaultValues | (value) | String | - |
| Yekonga.Helper.getValidFields | (input, validFields, isCreate, relations) | String | - |
| Yekonga.Helper.getUnderscore | (value) | String | - |
| Yekonga.Helper.isRelation | (value) | String | - |
| Yekonga.Helper.isBoolean | (value) | String | - |
| Yekonga.Helper.toPlural | (value) | String | - |
| Yekonga.Helper.toSingular | (value) | String | - |
| Yekonga.Helper.getTableByName | (value) | String | - |
| Yekonga.Helper.getPrimaryName | (schema) | String | - |
| Yekonga.Helper.getChildren | (schema) | String | - |
| Yekonga.Helper.getParents | (schema) | String | - |
| Yekonga.Helper.getRelationName | (table) | String | - |
| Yekonga.Helper.getAbbreviation | (name) | String | - |
| Yekonga.Helper.getTimestampInt | (value) | String | - |
| Yekonga.Helper.getIsoTimestamp | (value) | String | - |
| Yekonga.Helper.toTimestampString | (value, format = 'YYYY-MM-DD HH:mm:ss') | String | - |
| Yekonga.Helper.getTimestamp | (format = 'YYYY-MM-DD HH:mm:ss') | String | - |
| Yekonga.Helper.getDate | (format = 'YYYY-MM-DD') | String | - |
| Yekonga.Helper.getTime | (format = 'HH:mm') | String | - |
| Yekonga.Helper.copyJson | (value) | String | - |
| Yekonga.Helper.isGeneralTable | (data, ignore = false) | String | - |
| Yekonga.Helper.isId | (name) | String | - |
| Yekonga.Helper.isColumnUrl | (name) | String | - |
| Yekonga.Helper.isColumnMultiple | (name) | String | - |
| Yekonga.Helper.isLongText | (name) | String | - |
| Yekonga.Helper.isTimestampColumn | (name, data) | String | - |
| Yekonga.Helper.isSearchColumn | (name, data) | String | - |
| Yekonga.Helper.isTypeColumn | (name, data) | String | - |
| Yekonga.Helper.isMap | (name) | String | - |
| Yekonga.Helper.isNumeric | (value) | String | - |
| Yekonga.Helper.getGraphqlType | (name, field, isInput = false) | String | - |
| Yekonga.Helper.getMongodbType | (name, field) | String | - |
| Yekonga.Helper.getMongodbDefault | (name, field) | String | - |
| Yekonga.Helper.getType | (name, value) | String | - |
| Yekonga.Helper.getInput | (name, type) | String | - |
| Yekonga.Helper.bcryptPassword | (value) | String | - |
| Yekonga.Helper.attemptLogin | async (key, value, password, type = "normal") | String | - |
| Yekonga.Helper.getLoginData | async (user, accountId = null) | String | - |
| Yekonga.Helper.getToken | (payload) | String | - |
| Yekonga.Helper.getRelatedTable | (value) | String | - |
| Yekonga.Helper.getSchemaOf | (name) | String | - |
| Yekonga.Helper.schemaToData | (name) | String | - |
| Yekonga.Helper.getTableDataWithRelations | () | String | - |
| Yekonga.Helper.translate | async (content, from, to) | String | - |
| Yekonga.Helper.colorize = function colorize(color, output) | String | - |
| Yekonga.Helper.printLog | (title, text, color = 'white', newLine = true) | String | - |
| Yekonga.Helper.log | (title, text, color = 'white') | String | - |
| Yekonga.Helper.logInline | (title, text, color = 'white') | String | - |
| Yekonga.Helper.wait | async (time = 10000) | String | - |
| Yekonga.Helper.hasManyThrough | (child, data) | String | - |
| Yekonga.Helper.addSlashes | (value) | String | - |
| Yekonga.Helper.escape | (value) | String | - |
| Yekonga.Helper.validateEmail | (email) | String | - |
| Yekonga.Helper.isEmail | (email) | String | - |
| Yekonga.Helper.translateProcess | async (live, locale, lang, flag) | String | - |
| Yekonga.Helper.createLanguage | async (db, locale, lang, flag) | String | - |
| Yekonga.Helper.saveTranslation | async (db, row) | String | - |
| Yekonga.Helper.savePermission | async (db, row) | String | - |
| Yekonga.Helper.translateBandle | async (trans, locale, db) | String | - |
| Yekonga.Helper.encryptUrl | (body) | String | - |
| Yekonga.Helper.decryptUrl | (body) | String | - |
| Yekonga.Helper.isPermitted | (key, defaultValue = false) | String | - |
| Yekonga.Helper.sendMail | async (options) | String | - |
| Yekonga.Helper.getEmailContent | (name, content) | String | - |
| Yekonga.Helper.getRegistrationEmail | async (name, content) | String | - |
| Yekonga.Helper.getResetPasswordEmail | async (name, content) | String | - |

## Graphql
### Custom Action
#### Action for approve user example
```js
Yekonga.Cloud.setAction('User', 'approve', async ({params})=>{
    const {where, action, accessRole } = (params)? params: {};

    return await Yekonga.DataModel.User.update({ status: 1}, where, null, true);
})
```
The mutation for above will be as shown below
```graphql
mutation {
    userAction(where:{userId:{equalTo:"xxx"}}, action:"approve") {
        status
        message
    }
}
```

    
>
> * `User` is class name
> * `approve` is action name, can be anything `string`
>


### Custom Graphql
Custom graphql schema must be configued on the `customTypes` in [Graphql Configuration](#Graphql-Configuration) in config file
    
> Note: for auth Custom graphql resolver must be configued on the `customAuthTypes` in [Graphql Configuration](#Graphql-Configuration) in config file
>

#### Graphql schema example
```graphql
type BlockUserResponse {
    status: Boolean,
    success: Boolean
}

extend type Query {
    blockUser ( userId: String! ): BlockUserResponse,
}

```
Custom graphql resolver must be configued on the `customResolvers` in [Graphql Configuration](#Graphql-Configuration) in config file
    
> Note: for auth Custom graphql resolver must be configued on the `customAuthResolvers` in [Graphql Configuration](#Graphql-Configuration) in config file
>


#### Graphql resolver example
```js
module.exports = {
    Query: {
        blockUser: async function(parent, params, context) {
            // code goes here

            return  {status: false, success: false}
        }
    }
}

```

## Authors
Contributors names and contact info
ex. Robert Konga  
ex. [@robertkonga](https://twitter.com/robertkonga)
## Version History
* 2.0.0
* Various bug fixes and optimizations
* See [commit change]() or See [release history]()
* 1.1.0
* Initial Release
## License
This project is licensed under the [NAME HERE] License - see the LICENSE.md file for details
<!-- ## Acknowledgments
Inspiration, code snippets, etc.
* [awesome-readme](https://github.com/matiassingers/awesome-readme)
* [PurpleBooth](https://gist.github.com/PurpleBooth/109311bb0361f32d87a2)
* [dbader](https://github.com/dbader/readme-template)
* [zenorocha](https://gist.github.com/zenorocha/4526327)
* [fvcproductions](https://gist.github.com/fvcproductions/1bfc2d4aecb01a834b46) -->
