Awesome community module

fib-app

fibjs application basic api framework

Install

1
npm install fib-app [--save]

Test

1
npm test

Build a basic script

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
const http = require('http'); const util = require('util') const Session = require('fib-session') const App = require('../'); var app = new App('sqlite:test.db', { uuid: true }); app.db.use(require('./defs/person')); var session = new Session(new util.LruCache(20000), { timeout: 60 * 1000 }); var svr = new http.Server(8080, [ session.cookie_filter, { '/1.0': app } ]); svr.run();

Among them, person is the Model definition module, the content is as follows:

1 2 3 4 5 6 7
module.exports = db => { db.define('person', { name: String, sex: ["male", "female"], age: Number }); };

This is a standard orm definition, and other functions of orm can also be used, such as type checking, events, etc.

API data format

For POST and PUT requests, the body of the request must be in JSON format, and the Content-Type of the HTTP header must be set to application/json.

1 2 3 4
curl -X PUT \ -H "Content-Type: application/json" \ -d '{"name": "tom","sex":"male","age":23}' \ http://localhost/1.0/person/57fbbdb0a2400000

For all requests, the response format is a JSON object.

The success of a request is indicated by the HTTP status code. A 2XX status code indicates success, and a 4XX indicates failure of the request. When a request fails, the body of the response is still a JSON object, but it will always contain two fields, code and message, which you can use for debugging. For example, if a request for permission authentication fails, the following information will be returned:

1 2 3 4
{ "code": 4030501, "message": "The operation isn’t allowed for clients due to class-level permissions." }

The code is divided into three parts. The first three digits 403 indicate the error type, 05 indicates the data table number, and 01 indicates the detailed error code.

For GET requests, object data is usually returned. Depending on the address of the GET request, an object or an array may be returned. such as:

1 2 3 4 5
{ "name": "tom", "sex": "male", "age": 23 }

or:

1 2 3 4 5 6 7 8 9 10 11 12
[ { "name": "tom", "sex": "male", "age": 23 }, { "name": "lily", "sex": "female", "age": 22 } ]

Special field

In the object data, there are four fields with special meaning, which are not allowed to be changed through the API. They are id , updatedAt , createdAt , createdBy .

The id , updatedAt , createdAt single fields will be automatically created and modified. createdBy needs to specify the type by itself.

Basic object access API

After completing this data definition, you will directly have a complete set of interface calls that comply with the REST api specification:

url method action
/1.0/:className POST Create new object
/1.0/:className/:id GET Read object
/1.0/:className/:id PUT Modify object
/1.0/:className/:id DELETE Delete object
/1.0/:className GET Query object list

Create new object

In order to create a new object, a POST request should be sent to the URL of the class, which should contain the object itself. For example, to create the object as mentioned above:

1 2 3 4
curl -X POST \ -H "Content-Type: application/json" \ -d '{"name": "tom","sex":"male","age":23}' \ http://localhost/1.0/person

When the creation is successful, the HTTP response is 201 Created, and the body of the response is a JSON object, including the objectId and createdAt timestamp of the new object:

1 2 3 4
{ "createdAt": "2017-11-25T01:39:35.931Z", "id": "57fbbdb0a2400000" }

Read object

When you create an object, you can get its content by sending a GET request to the Location of the returned header. For example, to get the object we created above:

1
curl -X GET http://localhost/1.0/person/57fbbdb0a2400000

The returned body is a JSON object containing all user-provided fields plus the createdAt , updatedAt and id fields:

1 2 3 4 5 6 7 8
{ "name": "tom", "sex": "male", "age": 23, "createdAt": "2017-11-25T01:39:35.931Z", "updatedAt": "2017-11-25T01:39:35.931Z", "id": "57fbbdb0a2400000" }

By setting field returns keys , customized content can be returned, keys to a content to , segmentation field name strings:

1
curl -X GET http://localhost/1.0/person/57fbbdb0a2400000?keys=name%2Csex

Will return:

1 2 3 4
{ "name": "tom", "sex": "male" }

Modify object

In order to change the existing data of an object, you can send a PUT request to the corresponding URL of the object. Any key you have not specified will not be changed, so you can only update a subset of the object data. For example, let's change an age field of our object:

1 2 3 4
curl -X PUT \ -H "Content-Type: application/json" \ -d '{"age": 25}' \ http://localhost/1.0/person/57fbbdb0a2400000

The returned JSON object will contain updatedAt and id fields, indicating when the update occurred:

1 2 3 4
{ "updatedAt": "2017-11-25T01:39:35.931Z", "id": "57fbbdb0a2400000" }

Delete object

In order to delete an object, you can send a DELETE request to the URL of the specified object, such as:

1
curl -X DELETE http://localhost/1.0/person/57fbbdb0a2400000

Query object list

By sending a GET request to the URL of the class, without any URL parameters, you can get multiple objects at once. The following is simply to get all users:

1
curl -X GET http://localhost/1.0/person

The returned value is a JSON object containing the results field, and its value is a list of objects:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
[ { "name": "tom", "sex": "male", "age": 23, "createdAt": "2017-11-25T01:39:35.931Z", "updatedAt": "2017-11-25T01:39:35.931Z", "id": "57fbbdb0a2400000" }, { "name": "lily", "sex": "female", "age": 22, "createdAt": "2017-11-25T01:39:35.931Z", "updatedAt": "2017-11-25T01:39:35.931Z", "id": "57fbbdb0a2400001" } ]

keys field customization

Like object query, you can customize the fields contained in the returned result by setting keys when querying the list. keys of a content to , segmentation field name strings, for example:

1
curl -X GET http://localhost/1.0/person?keys=name%2Cage

Will specify to return only the two fields name and age .

where filter condition

The query object can be restricted by the form of where parameter.

The value of the where parameter should be JSON-encoded. In other words, if you look at the URL request that is actually sent, it should be JSON-encoded first, and then URL-encoded. The easiest way to use the where parameter is to include the appropriate key and value. For example, if we want to search for users whose name is tom, we should construct the query like this:

1
curl -X GET http://localhost/1.0/person?where=%7B%22name%22%3A%22tom%22%7D

The value of where is a JSON string after urlencode, the content is: {"name":"tom"}

In addition to exactly matching a given value, where also supports comparison methods, such as inclusion. where parameter supports the following options:

key operation sample
eq equal {"name":{"eq":"tom"}} or {"name":"tom"}
ne not equal to {"name":{"ne":"tom"}}
gt more than the {"age":{"gt":"24"}}
gte greater or equal to {"age":{"gte":"24"}}
lt Less than {"age":{"lt":"24"}}
lte Less than or equal to {"age":{"lte":"24"}}
like Fuzzy query {"name":{"like":"%m"}}
not_like Fuzzy query {"name":{"not_like":"%m"}}
between Interval comparison {"age":{"between":[22,25]}}
not_between Interval comparison {"age":{"not_between":[22,25]}}
in enumerate {"name":{"in":["tom","lily"]}}
not_in enumerate {"name":{"not_in":["tom","lily"]}}
or OR operation {"or":[{"name":"tom"},{"age":24}]}

skip skip record

Through the skip option, you can skip the specified number of records to achieve the effect of page turning.

1
curl -X GET http://localhost/1.0/person?skip=100

limit returns the record limit

Through the limit option, you can limit the number of returned records. The valid number of limit is 1-1000, and the default is 100.

1
curl -X GET http://localhost/1.0/person?limit=100

order specifies the sorting method

Use the order option to set the sorting method of the returned result set. If the field name contains - before it, it is in reverse order.

1
curl -X GET http://localhost/1.0/person?order=-id

count returns the total number of results

Increasing count when requesting can return the total number of result sets while returning the specified content.

1
curl -X GET http://localhost/1.0/person?count=1&limit=1

At this time, the returned result will contain two fields, count and results , which contain the total and result respectively:

1 2 3 4 5 6 7 8 9 10 11 12 13
{ "count": 2, "results": [ { "name": "tom", "sex": "male", "age": 23, "createdAt": "2017-11-25T01:39:35.931Z", "updatedAt": "2017-11-25T01:39:35.931Z", "id": "57fbbdb0a2400000" } ] }

Create extension objects

By defining hasOne and hasMany through orm, the relationship between objects can be defined and reflected in the API, for example:

1 2 3 4 5 6 7 8 9
module.exports = db => { var Person = db.models.person; var Pet = db.define('pet', { name: String }); Person.hasMany('pets', Pet); };

Extended object access API

The following is the API definition of the extension object:

url method action
/1.0/:className/:id/:extendName PUT Set extension object
/1.0/:className/:id/:extendName POST Create an extension object
/1.0/:className/:id/:extendName/:rid GET Read extended object
/1.0/:className/:id/:extendName/:rid PUT Modify the extension object
/1.0/:className/:id/:extendName/:rid DELETE Delete extension object
/1.0/:className/:id/:extendName GET Query the list of extended objects

Set extension object

Setting an extended object is to establish a connection between two independent objects. For example, Tom has adopted a pet named cat, which can be achieved with the following operations:

1 2 3 4
curl -X PUT \ -H "Content-Type: application/json" \ -d '{"id": "57fbbdb0a2400007"}' \ http://localhost/1.0/person/57fbbdb0a2400000/pets

In the call, the id of cat needs to be specified in the body.

Create an extension object

By directly creating extended objects, you can establish connections between objects while creating objects. such as:

1 2 3 4
curl -X POST \ -H "Content-Type: application/json" \ -d '{"name": "cat"}' \ http://localhost/1.0/person/57fbbdb0a2400000/pets

A pet named cat will be created and an association relationship with tom will be established.

Read extended object

Reading extended objects is very similar to reading base objects, and also supports the keys option:

1
curl -X GET http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007

Modify the extension object

Reading extended objects is very similar to reading base objects:

1 2 3 4
curl -X PUT \ -H "Content-Type: application/json" \ -d '{"name": "cat 1"}' \ http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007

Delete extension object

Deleting an extended object does not delete the object itself, but only removes the relationship between the objects:

1
curl -X DETELE http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007

Query the list of extended objects

Querying the extended object list is very similar to querying the basic object list, and also supports options such as keys and conditional filtering:

1
curl -X GET http://localhost/1.0/person/57fbbdb0a2400000/pets

ACL

You can control data permissions by defining Model ACLs. such as:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
const orm = require('fib-orm'); module.exports = db => { db.define('blog', { title: String, detail: Stringnote: String }, { ACL: function(session) { return { "*": { "*": false }, "57fbbdb0a2400000": { "*": true }, "roles": { "user": { "read": true } } }; } }); };

If no ACL is specified when defining the Model, it is equivalent to setting the default permissions:

1 2 3 4 5
{ "*": { "*": true } }

main body

There are three types of ACL subject descriptions, user id , user role and * . id represents a specific user, role represents a user with a certain role, and * represents all users:

main body description priority
id Specific user id 1
role User group name 2
* All 3

When checking permissions, you will first match id permissions correspond, if not specified, matching user role corresponding authority, if still specified, it looks at whether the * authority, if * is also not specified, do not have permission.

For example, in the above permission configuration, the user user group is specified to be able to read, user 57fbbdb0a2400000 has all permissions, while other users do not have any permissions.

Authority

ACL categorizes five types of permissions based on API behavior:

Authority description Allowed type
create Create object true / false / array
read Read object true / false / array
write Modify object true / false / array
delete Delete object true / false
find Query object list true / false
* Match all permissions true / false / array

The permissions are set to true to allow access, false to prohibit access, and array to allow access to only specified fields. delete and find do not accept array , if array is set, it is regarded as true . If the specified authority does not exist, it will match the * authority under the same principal. If none exists, query the subject with the next priority again.

For example, in the above example, if you need to set that the user only allowed to read the title and detail , and other people can read the title , you can set it like this:

1 2 3 4 5 6 7 8 9 10 11 12 13 14
{ "*": { "*": false, "read": ['title'] }, "57fbbdb0a2400000": { "*": true }, "roles": { "user": { "read": ['title', 'detail'] } } }

Object permissions

The permissions of the entire class are set on the Model. If you need to set permissions for specific objects, you can set OACL to achieve:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
module.exports = db => { db.define('person', { name: String, sex: ["male", "female"], age: Number }, { ACL: function(session) { return { "*": { "*": false } } }, OACL: function(session) { var _acl = {}; if(this.id === session.id) _acl[session.id] = { "*": true }; return _acl; } }); };

In this example, when the visitor is the object himself, all operations will be allowed, otherwise all visits are prohibited. The permissions will be checked according to the following steps:

  • person[57fbbdb0a2400000] => OACL
  • person => ACL

Extended object permissions

The access permission control of the extended object is similar to the basic object permission, the only difference is that it needs to be specified separately in the ACL:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
module.exports = db => { var Person = db.define('person', { name: String, sex: ["male", "female"], age: Number },{ ACL: function(session) { return { "*": { "read": ['name', 'sex'], "extends": { "pets": { "read": true, "find": true } } } } }, OACL: function(session) { var _acl = {}; if(this.id === session.id) _acl[session.id] = { "*": true, "extends": { "pets": { "*": true } } }; return _acl; } }); var Pet = db.define('pet', { name: String }); Person.hasMany('pets', Pet); };

In this definition, anyone can check the name and sex personal information, and freely check and search his pets . The user himself can manipulate all his own data and has all rights to his pet information.

When checking the access authority of the extended object, the object authority and the extended object authority are checked separately. For example, the following request:

1
curl -X GET http://localhost/1.0/person/57fbbdb0a2400000/pets/57fbbdb0a2400007

Will check permissions according to the following steps:

  • pets[57fbbdb0a2400007] => OACL
  • person[57fbbdb0a2400000] => OACL => extends => pets
  • person => ACL => extends => pets
  • pets => ACL

Function

APIs can be defined for the Model, and complex data operations can be completed by customizing the Function.

The vast majority of permissions can be controlled by ACL, and no function is required to complete object-based permissions. Function can be used to complete data-based permissions, such as granting permissions to different user groups based on approval status. And multiple modifications, such as the need to modify multiple database records.

Draw the data model

After completing the data definition, you can use app.diagram() draw the svg format class diagram of the data model, and save it to the file to get an image similar to the following:diagram