incore v1.0.8
Available languages, outros idiomas
Incore
Intelligent Core
Incore is an intelligent library that understands alone what you want through json instructions sent from the front and performs all the necessary queries, does joins, manage conflicts, updates, login, records, roles, claims, manage uploads, cryptography, manage products, addresses, zipcode, cities, states, pagination returning even the list of pages to be displayed, creates all routes easily
With Incore it is possible to have an API that would take a week to do in a few hours
It has two sides, a backend (this) and a front version, both talk to each other, but this backend version does not depend on the other
Installation
npm install --save incore
Setup
Create a new file called Incore.json in the same place as package.json in the API root folder
incore.json
This Json structure implements the interface IncoreConfig of which it has 4 Environments: development, production, test e preview, Each Environment implements the interface IncoreConfigEnv
Copy the code below and paste it in your incore.json and modify accordingly your needs
The knex property is a default knex configuration and accepts all parameters from it
The accessTokenExpiresIn and refreshTokenExpiresIn properties are expressed in seconds or string with time interval zeit/ms. Ex.: 60, "2 days", "10h", "7d"
{
"development": {
"knex": {
"client": "mysql",
"connection": {
"charset": "utf8",
"timezone": "America/Sao_Paulo",
"host": "localhost",
"database": "my_database",
"password": "123456",
"port": 3306,
"user": "root"
},
"pool": { "min": 2, "max": 10 },
"migrations": {
"directory": "src/databases/migrations",
"tableName": "migrations"
},
"seeds": { "directory": "src/databases/seeds" }
},
"auth": {
"accessTokenExpiresIn": "24h",
"refreshTokenExpiresIn": "3d",
"tokenSecretKey": "The secret for HMAC algorithms, or the PEM encoded private key for RSA and ECDSA",
"refreshTokenSecretKey": "...",
"algorithm": "HS256"
},
"uploads": {
"path": "/var/www/html/site/",
"baseUrl": "http://example.com"
},
"routesRoles": {
"/users/": ["admin"],
"/create": ["*"],
"/del": ["admin"],
"/delete": ["admin"],
"/signup": ["*"],
"/v1/cars/create": ["admin"],
"/users/create": ["admin"],
"r:REGEX_PATTERN": ["admin"]
},
"newUsersRoles": ["users", "members"],
"encryptId": true
},
"test": {},
"preview": {},
"production": {}
}
In the routesRoles property it is possible to control the roles in routes in general, to control specific actions you will use the roleMiddleware. If you start and end a path in routeRoles with "/", example: "/users/" it means that to list everything it will be restricted, it will only list if it sends an ID
In auth.algorithm the same values used by JWT are accepted
encryptId determines whether the results ID will return encrypted, ID encryption helps with security, but if you need the number, set this property to false
"HS256" | "HS384" | "HS512" | "RS256" | "RS384" | "RS512" |
"ES256" | "ES384" | "ES512" | "PS256" | "PS384" | "PS512" | "none"
By default, HS256 (HMAC - SHA256) is set
After the first run a knexfile.js file will be generated
Creating tables, routes in few lines
const app = async () => {
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
let server: Server;
const port = PORT || 3000;
// ---| Required |---
const env = process.env.NODE_ENV || "development";
// Use after: Incore.isTest | Incore.isDev | Incore.isProd | Incore.isPreview
Incore.env = IncoreEnv[env] || IncoreEnv.development;
// It creates tables, routes, data and more
await Incore.bootstrap();
// Set version path or just /
app.use("/v1", Incore.router);
// ---| Required |---
app.listen(port, () => {
console.log(`Server is running on PORT: ${port}`);
});
};
export default app;
A complete api was created with just these lines above, with authentication, user tables already with users in it, products, addresses, cities and states, currently with all cities and states in Brazil
The test users created have the password asd
After these lines above, it is already possible to make general requests, registrations, uploads, authentication and others
Here we are using local address http://localhost:3000 for examples
What are Instructions and actions?
Incore works through instructions sent in JSON or query in GET requests, most instructions can be sent in the GET method, example: ?limit=10&offset=0&id=any&page=1
To allow sending complex instructions it is possible to make READ requests using POST
Instruções
Property | Type | Description |
---|---|---|
action | string | CREATE, READ, UPDATE, PATCH, DELETE, LOGIN, LOGOUT, SIGNUP, REFRESH |
updatePassword | object | To update passwords on updates |
filters | array | Filters with operators =, !=, >, <, <=, >= |
limit | string | Limit of results per request |
offset | integer | Initial position from which to return results |
id | string | Encrypted item id for READ, UPDATE (required), DELETE (required) |
first | boolean | true returns the first item of the result |
count | boolean | true to quickly get the total items for this requisition |
embed | string | {"embed": "media"} Returns relations in array or object form in a separate property in the response, this instruction replaces complex JOINS. relations are configured inside models, read more below |
search | object | {"search": {"term": "abc", "in": "name", "email"}} IncoreApiSearchInstruction object to perform searches |
data | object | Item data to be created or updated, mandatory in CREATE, UPDATE and PATCH actions |
metadata | array | array with name and val to be inserted or updated in metadata table simultaneously |
page | integer | Pagination, current page number |
totalPages | integer | Pagination, total number of pages |
order | object | Sort results by asc or desc |
passwordField | string | Login, when using a table that does not have the default field name "password" |
conflict | array | Array with object field, op (operator) and message, used in SIGNUP to block user registration if the same data already exists and returns "message". The op property is optional if the operator is equal to = |
login | string | Data to authenticate and log in, email, telephone or any other data from the users table |
password | string | Password for LOGIN, SIGNUP or if sent in UPDATE will be encrypted automatically |
Authentication
Knowing the role object
To know how to apply the roles in routes, in middleware and others, let's see what are the role object properties
Parameter | Type | Description |
---|---|---|
role_id | string | Encrypted Identification |
name | string | Name, example Moderators |
uniq_id | string | Unique identifier, this is what you will use in routesRoles and in newUsersRoles in incore.json |
description | string | Description |
Creating, updating and removing roles
Authorization bearer TOKEN
POST /roles/create (application/json)
{
"data": {
"name": "Moderators",
"uniq_id": "moderators",
"description": "Moderators group"
}
}
With this we can create our roles and leave them in storage to assign them to users
Assign or remove a user role
Have the role and user ID "on hands"
Authorization bearer TOKEN
POST http://localhost:3000/v1/users/roles/create
{
"data": {
"role_id": "...",
"uid": "..."
}
}
Deleting
DELETE http://localhost:3000/v1/users/roles/del?id=ENCRYPTED_ID
Protect this route in the incore.json file in routesRoles delegating who can create or add users, add these
"/users/roles/create"
"/users/roles/del"
"/users/roles/update"
To return the user's roles when for example doing login, just send the instruction {"embed":"roles.role"}
It is always necessary to send the embed like roles.role so that it is possible to access the role object later in the response: data.item.roles0.role.uniq_id
To send more than one relation in the embed statement, do this:
{
"embed": "[media, roles.[role], addresses.[district,city,state]]"
}
In Incore, a user can have more than one address per group, for example home, work and others
Authenticating
POST http://localhost:3000/v1/auth/login
{
"login": "email@example.com",
"password": "123456",
"embed": "[media, roles.[role], addresses.[district,city,state]]"
}
{
"data": {
"item": {
"uid": "ENCRYPTED_ID",
"name": "John John",
"first_name": "John",
"last_name": "John",
"[...]": "[...]",
"addresses": [
{
"[...]": "[...]",
"city": {},
"state": {},
"district": {},
}
],
"roles": [
{
"[...]": "[...]"
}
],
"media": [
{
"[...]": "[...]"
}
]
},
"code": 200,
"token": {
"data": "...",
"expiresIn": "24h"
},
"refreshToken": {
"data": "...",
"expiresIn": "3d"
}
},
"message": ""
}
Keep in mind that only "addresses", "roles" and "media" were returned because they were all sent in the "embed" statement
The response implements the IncoreResponse<IncoreApiResponse\<T>> interface
Refresh the token with refreshToken
POST http://localhost:3000/v1/auth/refresh
Send the refresh token in the Authorization bearer header
User registration
POST http://localhost:3000/v1/auth/signup
{
"data": {
"...": "..."
},
"conflict": [
{
"field": "email",
"op": "=",
"message": "This email already exists"
}
]
}
User properties
Send the following properties in "data" in JSON
* (mandatory)
Property | Type | Description |
---|---|---|
name | string | * Username |
first_name | string | First name |
last_name | string | Last name |
nickname | string | Nickname |
social_addr | string | (Do not send) Created automatically by Incore when registering |
trade_name | string | Business name |
corporate_name | string | Corporate Name |
company_open_date | string | Company opening date |
company_activity_code | string | Example: CNAE |
company_legal_nature | string | Legal Nature |
account_type | string | individual or merchant |
company_capital | integer | Company capital |
birth_date | string | * Date of birth |
phone_number | string | Phone number |
cpf | string | Registration of natural persons |
rg | string | General registration |
ein | string | Employer Identification Number |
ssn | string | Social Security Number |
cnpj | string | Company registration number |
email | string | * Email address |
professional_document_number | string | Professional document number |
about | string | About User |
latitude | (10, 8) | Latitude (decimal) |
longitude | (11, 8) | Longitude (decimal) |
password | string | Password |
The "conflict" property is for defining fields that cannot receive repeated entries
In "op" are operators that we can use: =, !=, >=, <, <=
Imagine that we don't want the same social security number to repeat and neither does the email
We do it like this:
You can omit the operator if it is =
{
"data": {
"...": "..."
},
"conflict": [
{
"field": "email",
"message": "This email already exists"
},
{
"field": "ssn",
"message": "This number is already registered"
}
]
}
If it exists, Incore will return BAD_REQUEST (400) with the defined message
What do we understand so far?
We understand that to register things we must send the object "data" and I can optionally send the property "conflict"
We also know that to UPDATE we must send the "data" object and the item ID in JSON (id) or path
To do DELETE we must send the item ID in JSON (id) or in the path
Structure created by Incore
After running await Incore.bootstrap() for the first time the following structure was created internally
- TABLES
- metadata
- users (containing 30 users (password: asd))
- media (for video and image, see uploads bellow)
- countries
- country_states
- cities
- addr_groups
- addresses
- districts
- products
- products_categories
- categories
- tags
- notifications
- roles (contento: admin, members, users)
- user_roles
- coupons
- professions
- cart
- cart_items
- merchants_config
- ROUTES
- /metadata
- /auth
- /data
- /media
- /users
- /roles
- /users/roles
- /products
- /products/categories
- /categories
- /tags
- /cart
- /cart/items
- /merchant/config
- /notifications
- /countries
- /countries/states
- /countries/states/cities
- /countries/states/cities/districts
- /address
- /address/zipcode
- /address/groups
- /notifications
- /coupons
- /professions
Every route created by Incore has CRUD endpoints
CRUD example
http://localhost:3000/v1/users
http://localhost:3000/v1/users/create
http://localhost:3000/v1/users/update
http://localhost:3000/v1/users/update/1
http://localhost:3000/v1/users/del
http://localhost:3000/v1/users/del/1
How to create tables in the database?
You can create your tables any way you want, the only requirement is that all your tables have these required fields otherwise it will give errors
Required fields
metadata_id
created_at
updated_at
status (default 1 (IncoreStatus.ACTIVE))
These don't need to be defined in your models as they are already part of the IncoreModel from which your models will extend
If you are creating your tables using knex migration, you can use Incore's helper function called tableDefaults to create default data for each table, this function has the following structure
export const tableDefaults = (knex: Knex, table: Knex.CreateTableBuilder) => {
table.string("metadata_id").nullable();
table.dateTime("created_at").defaultTo(knex.fn.now());
table.dateTime("updated_at").nullable();
table.integer("status", 10).notNullable().defaultTo(IncoreStatus.ACTIVE);
table.engine("InnoDB");
table.charset("utf8mb4");
table.collate("utf8mb4_0900_ai_ci");
};
If your logic has fields that are repeated then use the DRY pattern with this function, copy it and also add your own default fields without changing the ones that are already defined there, change its name so as not to conflict with the Incore standard
In practice within migration
const hasTable = await knex.schema.hasTable("my_table");
if (!hasTable) {
await knex.schema.createTable("my_table", (table) => {
table.bigIncrements("my_table_id").unsigned().primary();
// table....
// Helper to create default data
tableDefaults(knex, table);
});
}
Repeat for each table
Read more about knex migrations
Creating your routes and using middleware
First of all, you need to know that Incore has 3 middleware for access control, they are
authMiddleware | roleMiddleware | claimMiddleware
authMiddleware is for controlling authentication token and refreshToken (JWT), roleMiddleware is for granting access e.g. only to admin or other, claimMiddleware is for claims, e.g. granting access only to users of a certain age or others
Before creating a route, you need to create a Model
Each model necessarily needs to extend IncoreModel, this is also an objection model
If you still don't know objection, this is a powerful ORM that together with knex helps a lot
https://vincit.github.io/objection.js/
Basic model
Each model needs to have these two static properties, they are mandatory
tableName
idColumn
export class Car extends IncoreModel {
car_id?: number;
color: string;
model: string;
static tableName = "cars";
static idColumn = "car_id";
}
export type CarInterface = IncoreModelInterface<Car>;
See how simple it is to create an interface for this model, just below each model write:
export type CarInterface = IncoreModelInterface<Car>;
After that you can use this interface to type your data
const someData: CarInterface;
RECAP
- Create a model class
- set its properties
- set static tableName
- set static idColumn
- export type as above
If you want to define a json schema for validation, write it inside the model
static get jsonSchema() {
return {
type: 'object',
required: ['color', 'model'],
properties: this.properties({
car_id: { type: 'integer' },
color: { type: 'string' },
model: { type: 'string' },
}),
}
}
Important that "properties" calls this.properties to allow Incore to add the default properties created_at, updated_at, status and metadata_id
Relations for the "embed" statement
For these relationships, Incore uses objection.js
Read more here about relations
Example inside the model, needs to be static
Imagine that when I make a list of cars I want the driver and also the car documents, so imagine that I have already created another model Driver and another CarData
Imagine that I want only one driver of the car, for that I use the following
IncoreModel.HasOneRelation
// Resulting in:
const driver = data.item.driver;
What if I want more drivers to return?
IncoreModel.HasManyRelation
// Resulting in:
data.item.driver.forEach((d) => {
console.log(d.name);
});
Inside the model write the following:
static relationMappings = {
driver: {
relation: IncoreModel.HasOneRelation,
modelClass: Driver,
join: {
from: 'car.car_id',
to: 'driver.car_id',
},
},
docs: {
relation: IncoreModel.HasManyRelation,
modelClass: CarData,
join: {
from: 'car.car_id',
to: 'docs.car_id',
},
},
}
Quick Info:
Incore always returns "data.item" for results with "first" statement
or with instruction **"id"** and returns "data.items" in plural
for multiple results
After that, you won't have to worry about having to write queries with JOINS anymore, you'll just do it like this when sending the request:
{
"embed": "driver"
}
or to include docs, i.e. more than one relation
{
"embed": "[driver,docs]"
}
or if you still want to return relations from other models as well, write like this:
{
"embed": "[driver.[other, etc], docs]"
}
Note that "docs" uses IncoreModel.HasManyRelation which will return an array:
{
"data": {
"item": {
"...": "...",
"driver": {
"...": "...",
},
"docs": [
{
"...": "...",
}
]
}
},
}
data.item.driver
data.item.docs[0]
It is possible to do many more things within this model, keep reading
Now that we know how to create the model, let's create a route
You can write your routes for example in a file called routes.ts. Done that we will insert our routes in it, which will be just an array, we will create a route for our model Car that was created above
import { IncoreRouteConfig } from "incore";
export const myRoutes: IncoreRouteConfig[] = [
{
path: "/cars",
model: Car,
},
];
That's it, that's it for now, remembering that you need to follow the IncoreRouteConfig type.
Keep reading to learn how to use middleware in routes
Now let's go back to where we initialized everything and just above await Incore.bootstrap(), register your routes, see:
// const app = express()
// [...]
// Registre suas rotas
Incore.createRoutes(myRoutes);
await Incore.bootstrap();
After that we can already make requests on this route
http://localhost:3000/v1/cars
http://localhost:3000/v1/cars/create
http://localhost:3000/v1/cars/update
http://localhost:3000/v1/cars/del
http://localhost:3000/v1/cars/patch
Attention:
Do not write /create, /update, /del or /patch, just write "/cars" Incore will create them all automatically
GET http://localhost:3000/v1/cars
{
"first": true,
"filters": [[["color", "!=", "blue"]]],
"embed": "[driver,docs]"
}
POST http://localhost:3000/v1/cars/create
{
"data": {
"color": "blue",
"model": "luxury"
}
}
PUT http://localhost:3000/v1/cars/update
or if not send the "id" in the instruction
PUT http://localhost:3000/v1/cars/update/[ENCRYPTED_ID]
{
"id": "ENCRYPTED_ID",
"data": {
"color": "blue",
"model": "luxury"
}
}
Internally Incore created the following endpoints for "cars"
GET http://localhost:3000/v1/cars/
POST http://localhost:3000/v1/cars/
POST http://localhost:3000/v1/cars/create
UPDATE http://localhost:3000/v1/cars/update
DELETE http://localhost:3000/v1/cars/del
PATCH http://localhost:3000/v1/cars/patch
Control route with middleware
For each action it is possible to define different middleware, but first let's see how the names of the actions are written inside Incore
The actions come from the IncoreAction type and these below are the ones available at the moment
'CREATE' | 'READ' | 'UPDATE' | 'PATCH' | 'DELETE' | 'LOGIN' | 'LOGOUT' | 'SIGNUP' | 'REFRESH'
Now imagine that you don't want an unauthenticated user to register and update cars (we are in the Car model)
const handlers: IncoreApiRouteHandler[] = [
{
handler: authMiddleware,
},
{
handler: myOtherMiddleware,
args: ["arg1"],
},
];
const middleware: IncoreApiRouteMiddleware[] = [
{
action: "CREATE",
middleware: handlers,
},
{
action: "UPDATE",
middleware: handlers,
},
{
action: "DELETE",
middleware: handlers,
},
];
Above we already have our middleware that can now be added to as many routes as we want
As you may have noticed in "myOtherMiddleware", it is possible to send arguments that will be received in the middleware: (arg1, arg2, arg3)
Adding middleware to routes
export const myRoutes: IncoreRouteConfig[] = [
{
path: "/cars",
model: Car,
middleware: middleware,
},
{
path: "/other/path",
model: OtherModel,
middleware: middleware,
},
];
From now on when trying to register a car, update or delete it will only be possible if it is authenticated due to the middleware authMiddleware
Keep reading to learn how to create your own middleware
How to use roleMiddleware and claimMiddleware
// ROLES
// Must return RoleResponse type
// we create a separate function to stay organized
const rolesChecker: RoleMiddleware = (roles, instructions): RoleResponse => {
// Não permitir caso o usuário atual não seja admin
if (!roles.find(r => r.role.uniq_id === 'admin')) {
return {
granted: false,
code: IncoreResponseCode.FORBIDDEN,
message: 'message...',
}
}
return {
granted: true,
}
}
// CLAIMS
const claimsChecker: Claim = (user, instructions): ClaimResponse => {
const birthDate = new Date(user.birth_date)
const acceptOnlyNewUsers = new Date(user.created_at)
if (...) {
return {
granted: false,
code: IncoreResponseCode.FORBIDDEN,
message: 'message...',
}
}
return {
granted: true,
}
}
// We send rolesChecker and claimsChecker as an argument to the middleware
const handlers: IncoreApiRouteHandler[] = [
{
handler: authMiddleware,
},
{
handler: roleMiddleware,
args: [rolesChecker],
},
{
handler: claimMiddleware,
args: [claimsChecker],
}
]
const middleware: IncoreApiRouteMiddleware[] = [
{
action: 'CREATE',
middleware: handlers,
},
{
action: 'UPDATE',
middleware: handlers,
},
{
action: 'DELETE',
middleware: handlers,
},
]
Now assign "middleware" on as many routes as you want as seen above
Create your own middleware
A middleware must implement the IncoreApiMiddleware type returning a standard express middleware
must return
(req, res, nex) => {}
If you need to respond prematurely and block normal execution for some reason send IncoreResponse.json passing null as the data, an http status, the message and res or if everything is ok next() to continue as shown below
export const myMiddleware: IncoreApiMiddleware = (arg1: string) => {
return (req: IncoreExpressRequest, res: Response, next: NextFunction) => {
if (...) {
IncoreResponse.json(
null,
IncoreResponseCode.FORBIDDEN,
'Message...!',
res
)
return
}
// continue normally
next()
}
}
Use your new middleware same as shown with others, authMiddleware, roleMiddleware and claimMiddleware passing arguments if needed
Using your own queries
Calling methods inside your models by instruction via JSON
Imagine sending a JSON property with an array as a value and Incore calling a method inside your model with that name and the array arguments
Let's create a method called doSomething inside our model expecting 3 arguments, a string, another number and another boolean
Inside here you can create your own queries, they are knex normal, the difference is that you don't need to pass table name because the model already does that, also use all objection facilities
class MyModel extends IncoreModel {
prop: string;
static tableName = "my_table";
static idColumn = "id_col";
async doSomething(
arg1: string,
arg2: number,
arg3: boolean
): Promise<IncoreApiResponse<MyModelInterface> | null> {
const { data, params, offset, page } = this.instructions();
const expressRequest = this.repository().request;
// Write your own queries
// If you need a query from another table, use its model
const queryResult = await MyModel.query()
.where("..", "..")
.withGraphFetched(this.instructions().embed ?? "");
// Stop and return your own answer
if (data.email == "abc@example.com") {
return {
code: IncoreResponseCode.FORBIDDEN,
message: "...",
};
} else {
// return your own result
// use item in the singular if it is a single result
// for example when using .first()
// Calculate the total results
const total = 50;
const navigation = this.navigation(total, page);
return {
code: IncoreResponseCode.OK,
items: queryResult,
navigation: navigation,
};
}
// Change the instructions if needed
const newInstructions = {
...this.instructions(),
first: true,
};
// Pass the new instructions to the repository
this.repository().apiInstructions = newInstructions;
// continue normally
return null;
}
}
export type MyModelInterface = IncoreModelInterface<MyModel>;
Other properties that can be returned
return {
code: IncoreResponseCode.OK,
redirectTo: "http://example.com",
html: "<DOCTYPE html>...html code",
js: "JavaScript code",
text: "Text code",
};
Now let's call this method directly through JSON
POST http://localhost:3000/v1/my/endpoint/create
{
"doSomething": ["that simple", 1, true],
"data": {
"name": "John",
"email": "abc@example.com"
}
}
Uploads
With Incore it is very easy to deal with uploads, for that there is already a table called media for images and videos
To receive uploads there are two unique steps
1) Set path and baseUrl in incore.json file
{
"uploads": {
"path": "/var/www/html/site/",
"baseUrl": "http://example.com"
}
}
2) Adicionar dois middleware um após o outro
uploadService
mediaMiddleware
import { authMiddleware, uploadService, mediaMiddleware } from "incore";
const uploadMiddlewareHandlers: IncoreApiRouteHandler[] = [
{
handler: authMiddleware, // <--- if you need authentication
},
{
handler: uploadService, // <--- first this
},
{
handler: mediaMiddleware, // <--- then this
},
];
const uploadMiddleware: IncoreApiRouteMiddleware[] = [
{
action: "CREATE",
middleware: uploadMiddlewareHandlers,
},
{
action: "UPDATE",
middleware: uploadMiddlewareHandlers,
},
];
// Assign on your routes that need uploading
export const myRoutes: IncoreRouteConfig[] = [
{
path: "/cars",
model: Car,
middleware: uploadMiddleware,
},
{
path: "/cars/docs",
model: Docs,
middleware: uploadMiddleware,
},
];
In general you will only need to upload in CREATE and UPDATE actions, that is, POST and UPDATE
That's all you need! To get the images just send in the "embed" instruction like this
{
"embed": "media"
}
Will return all uploads with results
data.item.media.foreach((m) => {
console.log(m.url);
});
most necessary media properties
Property | Type | Description |
---|---|---|
media_id | string | Encrypted ID |
source | string | path to file on disk |
url | string | Full URL for image or video |
Others
type (1=image, 2=video), width, height, x, y, duration,
color_filter, format, mimetype, resolution, aspect_ratio,
bit_rate, frame_rate, channels, sampling_rate, commercial_name,
compression_mode, bit_depth, size
Filters
The filters in Incore completely replace WHERE and OR, they are of the array[], this way we can understand that there are two arrays inside, one for WHERE and another for OR
Imagine that we want all users with status equal to 2 and that the email starts with "john", our JSON looks like this:
Each filter has 3 values, the name, the operator and the value, the operator can be omitted, so that Incore understands that you want to use equal to (=)
POST http://localhost:3000/v1/users
{
"filters": [
[
["status", 2],
["email", "like", "john%"]
]
]
}
You can use all native LIKE patterns
Now let's add "OR"
{
"filters": [
[
["status", 2],
["email", "like", "john%"]
],
[
["status", 3],
["email", "like", "%jack%"]
]
]
}
In an SQL query these filters would look like this:
WHERE
(`status` = 2 AND `email` LIKE 'john%')
OR
(`status` = 3 AND `email` LIKE '%jack%')
You can use all operators: >, <, >=, <=, !=
{
"filters": [
[
["status", ">=", 2],
["email", "like", "john%"]
]
]
}
Metadata
In Incore each database table has a field called metadata_id, this value is created automatically when doing POST /create, that is, in the action CREATE
This id is related to the metadata table, so it is possible to add extra values for each entry, for example:
Imagine that I want to save the user's license plate number but this field does not exist in the users table, we will use the metadata for this:
Metadata structure
Property | Type | Description |
---|---|---|
metadata_id | string | Encrypted ID |
related_to_id | string | relation to metadata_id in other tables |
name | string | Name, example: license plate |
val | any | Value, example: 1234 |
There are two ways to save data in metadata
- Along with registering something
POST http://localhost:3000/v1/products/create
{
"data": {
"...": "..."
},
"metadata": [
{
"name": "val"
},
{
"name": "val"
}
]
}
- Separately on Incore's own route. You will need the metadata_id of related content
GET http://localhost:3000/v1/metadata
POST http://localhost:3000/v1/metadata
POST http://localhost:3000/v1/metadata/create
PUT http://localhost:3000/v1/metadata/update
We now set on "data"
POST http://localhost:3000/v1/metadata/create
{
"data": {
"related_to_id": "PRODUCT_METADATA_ID",
"name": "data_name",
"val": "value"
}
}
update
PUT http://localhost:3000/v1/metadata/update
{
"id": "METADATA_ID",
"data": {
"related_to_id": "PRODUCT_METADATA_ID",
"name": "data_name",
"val": "value"
}
}