entix-cli
Supported Commands
NOTE: items marked with *
are optional.
-
entix app
name_of_app_directory* -
entix api
name_of_entity -
entix migration
name_of_migration -
entix client
name_of_app_directory* npm run dl:db
npm run init
npm run dev
npm run start
npm run seed
Supported Flags
-
-v
: displays version -
-O
: overwrites old files
Entix App
Default Entix Controllers
Every Entix app comes loaded with the following base controllers. These are a collection of the most common tasks such as CRUD operations and querying relations. If you need to override one of these controllers you can do that by writing your replacement function in the controller.js
file:
- loadOne:
/users/:id
- loads entity inctx.state.user
. - find:
/users/:id?p=1&l=10
- returns all instnaces with pagination support . - findOne:
/users/:id
- returns a single instance. - findRelation:
/users/:id/roles
- returns all role instances related to the user instance with:id
. - $findRelation:
/user-roles
- returns all role instances related to the authenticated$user
instance. - count:
/users/count
- returns the number of user records in the databse. - create:
/users/:id
- creates a new user and returns a single instance. - update:
/users/:id
- updates the user with:id
returns the updated instance. - delete: deletes the user with
:id
and retuns true. - addRelation:
/users/:id/roles
-many to many
links roleId to user instance with:id
. - removeRelation:
/users/:id/roles
-many to many
unlinks roleId from user instance with:id
. - syncRelation:
/users/:id/roles
-many to many
syncs the user-roles relationship to a given array ofrole:ids
.
NOTE: findRelation
, $findRelation
, addRelation
, removeRelation
and syncRelation
rely on their route name to retrieve the relation that needs to be loaded. As such you should keep this in mind when naming your routes. Below is an example to illustrate this.
Example 1A: In this example we will consider an user entity
model.js
class User extends Model {
...
static get relationMappings(){
return {
emails: { ... },
roles: { ... },
}
}
}
This user model has two relaionships. Namely, emails
a has many
relation and roles
a many to many
relation
config.js
module.exports = {
routes: [
{
path: '/user-emails',
method: 'get',
handler: 'users.$findRelation',
middleware: [
'@restricted.authenticated',
]
},
{
path: '/users/:id/emails',
method: 'post',
handler: 'users.findRelation',
middleware: [
'@load.userById',
]
},
{
path: '/users/:id/roles',
method: 'post',
handler: 'roles.addRelation',
middleware: [
'@load.userById',
]
},
{
path: '/users/:id/roles',
method: 'delete',
handler: 'roles.removeRelation',
middleware: [
'@load.userById',
]
},
]
}
Notice that the handler of the /user-emails
route is preceded with a $
to inducate that it will draw return relations for the authenticated user. These routes are of the format /user-relation_name
. Instnace routes on the other require that you include the :id
of the instance in your route and should adhere to the format /user/:id/relation_name
. Consequently, this will return relations for the instance loaded with the provided :id
.
Example 2A: Models
Entities contain 3 files, one of which is the model.js
file. This file is used for writing DB schemas, relation mappings and your validation logic. In this section we will address each of these parts in detail.
model.js
class User extends Model {
...
static get relationMappings(){
return {
tenant: {
relation: Model.BelongsToOneRelation,
modelClass: require('./../tenants/model'),
join: {
from: 'users.tenant_id',
to: 'tenants.id'
}
},
emails: {
relation: Model.HasManyRelation,
modelClass: require('./../emails/model'),
join: {
from: 'emails.user_id',
to: 'users.id',
},
},
roles: {
relation: Model.ManyToManyRelation,
modelClass: require('./../roles/model'),
join: {
from: 'users.id',
to: 'roles.id',
through: {
from: 'user_roles.user_id',
to: 'user_roles.role_id',
}
},
},
}
}
}
model.js (knex schema)
class User extends Model {
static schema = (table) => {
table.increments()
table.string('user_id').unique().notNullable()
table.string('username').unique().notNullable()
table.string('password').unique().notNullable()
table.string('avatar_path').unique()
table.string('given_name')
table.string('family_name')
table.string('middle_name')
table.enu('sex', ['male', 'female'])
table.date('birth_date')
table.string('birth_place')
table.string('birth_country')
table.boolean('is_disabled').defaultTo(false)
table.timestamps(true,true)
}
...
}
model.js (knex schema:user_roles)
static schema = (table) => {
table.increments()
table.unique(['user_id', 'role_id'])
table.integer('user_id').references('id').inTable('users').onDelete('CASCADE').notNullable()
table.integer('role_id').references('id').inTable('roles').onDelete('CASCADE').notNullable()
}
model.js (joi validation)
class User extends Model {
static validation = {
createUser: v.object().options(joiOptions).keys({
username: v.string().min(3).max(30).required(),
email: v.string().email().required(),
password: v.string().min(6).required(),
}),
updateUser: v.object().options(joiOptions).keys({
given_name: v.string().min(1).label('given name').allow(null),
family_name: v.string().min(1).label('family name').allow(null),
middle_name: v.string().min(1).label('middle name').allow(null),
sex: v.string().allow(null),
birth_date: v.string().min(1).label('birth date').allow(null),
birth_place: v.string().min(1).label('birth place').allow(null),
birth_country: v.string().min(1).label('birth country').allow(null),
}),
}
...
}
Core Services
Uploads
async uploadAvatar(ctx) {
const { files, body } = ctx.request
const { user } = ctx.state
if(files && Object.keys(files).length > 0) {
const folder = resolve(__dirname, './../../../', 'public')
const oldPath = resolve(folder, user.avatar_path || 'placeholder.txt')
if(fs.existsSync(oldPath)) await fs.promises.unlink(oldPath)
const upload = await entix.services.upload.one({
files: files,
field: 'avatar',
folder: folder
})
if(!upload) ctx.throw(401, 'update failed')
body.avatar_path = upload.filename
}
const instance = await user.$query().patch(body)
ctx.body = instance
},