Awesome Community Module

fib-app

fibjs application basic api framework

Install

1
npm install fib-app [--save]

Test

1
npm test

Create 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();

Where personis 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 you can also use other ORM functions, such as type checking, events, etc.

API data format

For POST and PUT requests, the request body must be in JSON format, and the Content-Type of the HTTP header needs to 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.

Whether a request was successful or not is indicated by the HTTP status code. A 2XX status code indicates success, while a 4XX status code indicates that the request failed. When a request fails, the body of the response is still a JSON object, but it always contains the code and message fields, 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 code is divided into three parts. The first three digits 403 represent the error type, 05 represents the data sheet number, and 01 represents 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. for example:

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 fields

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

Among them id, updatedAt, createdAtsingle fields will be automatically created and modified. createdByYou need to specify the type yourself.

Basic object access API

After completing such data definition, you will directly have a 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

To create a new object, a POST request should be sent to the class's URL, which should include the object itself. For example, to create the object shown 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 return is 201 Created, and the body of the response is a JSON object containing 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 retrieve its contents by sending a GET request to the Location in 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-supplied fields plus the createdAt, updatedAtand idfields:

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 the return field keys, you can customize the returned content, keyswhich is a ,string of field names separated by:

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 an object's existing data, you can send a PUT request to the corresponding URL of the object. Any keys you do not specify will not be changed, so you can only update a subset of the object's 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 updatedAtand idfields indicating when the update occurred:

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

Delete object

To delete an object, you can send a DELETE request to the specified object's URL, for example:

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

Query object list

You can get multiple objects at once by sending a GET request to the class's URL, without any URL parameters. Here's how to simply get all users:

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

The returned value is a JSON object containing the results field, whose 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 keyscustomize the fields included in the returned results by setting when querying the list. keysThe content is a ,string of field names separated by , for example:

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

Will specify that only the nameand agefields will be returned.

where filter condition

whereThe query object can be constrained in the form of parameters.

whereThe parameter value should be JSON encoded. That is, if you look at the actual URL request being made, it should be JSON-encoded first and then URL-encoded. The simplest whereway to use parameters is to include the appropriate key and value. For example, if we wanted to search for users named tom, we would construct the query like this:

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

whereThe value is a urlencoded JSON string, the content is:{"name":"tom"}

In addition to matching a given value exactly, wherecomparison methods such as inclusion are also supported. whereParameters support 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 records

Through skipthe 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 record limit

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

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

order specifies the sorting method

Use orderthe option to set the sorting method of the returned result set. When 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

Incremented on request countThe total number of resultsets that can be returned 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: countand results, containing the total number 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 object

By defining hasOne and hasMany through ORM, you can define the association between objects and reflect it on 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 extension object
/1.0/:className/:id/:extendName/:rid GET Read extension object
/1.0/:className/:id/:extendName/:rid PUT Modify extension object
/1.0/:className/:id/:extendName/:rid DELETE Delete extended object
/1.0/:className/:id/:extendName GET Query extended object list

Set extension object

Setting an extension object is to establish a relationship between two independent objects. For example, if Tom adopts a pet named cat, he can use the following operations to achieve this:

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 cat's id needs to be specified in the body.

Create extension object

Directly creating extended objects can establish connections between objects while creating objects. for example:

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 associated with tom.

Read extension 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 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 extended object

Deleting an extended object does not delete the object itself, it only dissolves the relationship between the objects:

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

Query extended object list

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

Data permissions can be controlled by defining the Model's ACL. for example:

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 roleand *, idwhich represent a specific user, rolerepresent users with a certain role, and *represent all users:

main body describe priority
ID specific user id 1
role User group name 2
* All 3

When checking permissions, it will first match idthe corresponding permissions. If not specified, then match rolethe corresponding permissions of the user. If it is still specified, check whether *the permissions of are specified. If *not specified, there is no permission.

For example, in the above permission configuration, userthe user group is specified to be able to read. The user 57fbbdb0a2400000has all permissions, but other users do not have any permissions.

Permissions

ACL classifies permissions into five categories based on API behavior:

Permissions describe allowed types
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

Permissions are set trueto allow access, falseto deny access, and arrayto only allow access to specified fields. deleteand findare not accepted and are treated the same arrayif set . If the specified permission does not exist, the permissions under the same subject will be matched. If neither exists, query the subject of the next priority level again.arraytrue*

For example, in the above example, if you need to set useronly read titleand detailallow others to read 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 set on the Model are the permissions of the entire class. If you need to set permissions on specific objects, you can do so by setting OACL:

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 access will be prohibited. Permissions are checked as follows:

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

Extended object permissions

The access permission control of extended objects is similar to the permissions of basic objects. The only difference is that the ACL needs to be specified separately:

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 namesum of personal information sex, and freely check and search it pets. The user himself can operate all his own data and have full permissions for his own pet information.

When checking access permissions for extended objects, object permissions and extended object permissions are checked separately. For example, the following request:

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

Permissions are checked as follows:

  • 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 through custom Functions.

Most permissions can be controlled through ACL, and object-based permissions do not need to be implemented through Function. 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 data model

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