База данных имеет REST API для выполнения произвольных операций. Все основные действия с базой выполняются через данный интерфейс, в том числе выполняемые из веб-интерфейса базы.
Запросы производятся на URL, соответствующему адресу вашей базы, по шаблону: https://ИМЯ_БАЗЫ.api.ozma.org
.
Если это применимо, тело запроса должно быть в формате JSON. В таком случае необходимо добавлять заголовок Content-type: application/json
.
Если необходимо, данные возвращаются в формате JSON, в том числе и ошибки. Для результатов-ошибок в возвращаемом объекте всегда присутствуют поля:
"type"
: тип ошибки, в настоящий момент только "generic"
;"message"
: сообщение об ошибке.Механизм авторизации и аккаунтов ботов будет переработан в будущем.
Авторизация в систему производится по стандарту OIDC. Чтобы получить свой client id и secret, напишите письмо в [email protected].
После получения вы можете запросить токен авторизации, используя логин и пароль нужного пользователя (grant_type=password
). Например, для Python есть подходящая библиотека requests-authlib.
https://account.ozma.io/auth/realms/default/.well-known/openid-configuration
https://account.ozma.io/auth/realms/default/protocol/openid-connect/token
Токен (access token в терминах OIDC) следует отправлять в заголовках HTTP-запросов в API, используя Authorization: Bearer ВАШ_ТОКЕН
.
Пример запроса через curl:
curl \
-d "client_id=client" \
-d "client_secret=secret" \
-d "grant_type=password" \
-d "username=user" \
-d "password=password" \
"https://account.ozma.io/auth/realms/default/protocol/openid-connect/token"
Для проверки прав доступа к базе по токену можно использовать запрос GET /check_access
. Если токен верный и по нему разрешён доступ в эту базу, вернётся ответ 200 OK
.
Токен необходимо регулярно обновлять согласно стандарту OAuth, используя refresh_token
. Лучше доверить работу с токеном готовой библиотеке для вашего языка программирования. Пример запроса на обновление через curl:
curl \
-d "client_id=client" \
-d "client_secret=secret" \
-d "grant_type=refresh_token" \
-d "refresh_token=REFRESH_TOKEN" \
"https://account.ozma.io/auth/realms/default/protocol/openid-connect/token"
В ответе приходит не только новый access_token
, но и новый refresh_token
.
Возможно как делать запросы по существующим (именованным) отображениям, так и делать произвольные запросы на лету. Для всех типов отображений предоставляются несколько вызовов для получения данных и метаданных.
Путь к отображению: /views/by_name/СХЕМА_ОТОБРАЖЕНИЯ/ИМЯ_ОТОБРАЖЕНИЯ
.
Путь к отображению: /views/anonymous
.
Среди GET-параметров должен присутствовать параметр __query
, содержащий строку запроса.
Если вы планируете использовать запрос часто, вы достигнете большей производительности, создав именованный запрос. Также рекомендуется использовать аргументы в запросе вместо вставки значений напрямую в текст запроса. Так запрос будет кэшироваться, а вы сможете избежать атаки через инъекцию кода.
Запрос: GET ПУТЬ_К_ОТОБРАЖЕНИЮ/entries
.
Возвращает: IViewExprResult
(см. ниже)
В GET-параметры запроса можно передавать аргументы для отображения, например ?id=42
. Аргументы кодируются как JSON, например, строковый параметр передаётся так: foo="value"
.
Запрос: GET ПУТЬ_К_ОТОБРАЖЕНИЮ/info
.
Возвращает: IViewInfoResult
(см. ниже)
Следующие типы на языке TypeScript описывают формат возвращаемых данных:
// Вспомогательные типы.
export type DomainId = number;
export type RowId = number;
export type FieldName = string;
export type EntityName = string;
export type SchemaName = string;
export type ColumnName = string;
export type AttributeName = string;
export type UserViewName = string;
// Метаданные. Не нужны, если планируется только работа с заранее известными запросами без изменения данных.
export interface IEntityRef {
schema: SchemaName;
name: EntityName;
}
export interface IFieldRef {
entity: IEntityRef;
name: FieldName;
}
export interface IUserViewRef {
schema: SchemaName;
name: UserViewName;
}
export type SimpleType = "int" | "decimal" | "string" | "bool" | "datetime" | "date" | "interval" | "regclass" | "json";
export interface IScalarSimpleType {
type: SimpleType;
}
export interface IArraySimpleType {
type: "array";
subtype: SimpleType;
}
export type ValueType = IScalarSimpleType | IArraySimpleType;
export type FieldValueType = "int" | "decimal" | "string" | "bool" | "datetime" | "date" | "interval" | "json";
export type AttributeTypesMap = Record<AttributeName, ValueType>;
export interface IScalarFieldType {
type: FieldValueType;
}
export interface IArrayFieldType {
type: "array";
subtype: FieldValueType;
}
export interface IReferenceFieldType {
type: "reference";
entity: IEntityRef;
where?: string;
}
export interface IEnumFieldType {
type: "enum";
values: string[];
}
export type FieldType = IScalarFieldType | IArrayFieldType | IReferenceFieldType | IEnumFieldType;
export interface IColumnField {
fieldType: FieldType;
valueType: ValueType;
defaultValue: any;
isNullable: boolean;
isImmutable: boolean;
inheritedFrom?: IEntityRef;
}
export interface IMainFieldInfo {
name: FieldName;
field: IColumnField;
}
export interface IResultColumnInfo {
name: string;
attributeTypes: AttributeTypesMap;
cellAttributeTypes: AttributeTypesMap;
valueType: ValueType;
punType?: ValueType;
mainField?: IMainFieldInfo;
}
export interface IDomainField {
ref: IFieldRef;
field?: IColumnField;
idColumn: ColumnName;
}
export interface IResultViewInfo {
attributeTypes: AttributeTypesMap;
rowAttributeTypes: AttributeTypesMap;
domains: Record<DomainId, Record<ColumnName, IDomainField>>;
mainEntity?: IEntityRef;
columns: IResultColumnInfo[];
}
// Данные.
export type AttributesMap = Record<AttributeName, any>;
export interface IExecutedValue {
value: any; // Значение в ячейке.
attributes?: AttributesMap;
pun?: any;
}
export interface IEntityId {
id: RowId;
subEntity?: IEntityRef;
}
export interface IExecutedRow {
values: IExecutedValue[]; // Значения в строке.
domainId: DomainId;
attributes?: AttributesMap;
entityIds?: Record<ColumnName, IEntityId>;
mainId?: RowId;
mainSubEntity?: IEntityRef;
}
export interface IExecutedViewExpr {
attributes: AttributesMap;
columnAttributes: AttributesMap[];
rows: IExecutedRow[];
}
export interface IViewExprResult {
info: IResultViewInfo;
result: IExecutedViewExpr;
}
export interface IViewInfoResult {
info: IResultViewInfo;
pureAttributes: Record<AttributeName, any>;
pureColumnAttributes: Record<AttributeName, any>[];
}
Например, следующий запрос возвращает результат отображения funapp.settings
:
GET /views/by_name/funapp/settings/entries
Authorization: Bearer ВАШ_ТОКЕН
Результат:
{
"info":{
"attributeTypes":{},
"rowAttributeTypes":{},
"domains":{
"0":{
"name":{
"ref":{
"entity":{
"schema":"funapp",
"name":"settings"
},
"name":"name"
},
"field":{
"fieldType":{
"type":"string"
},
"valueType":{
"type":"string"
},
"isNullable":false,
"isImmutable":false
},
"idColumn":"name"
},
"value":{
"ref":{
"entity":{
"schema":"funapp",
"name":"settings"
},
"name":"value"
},
"field":{
"fieldType":{
"type":"string"
},
"valueType":{
"type":"string"
},
"isNullable":false,
"isImmutable":false
},
"idColumn":"name"
}
}
},
"columns":[
{
"name":"name",
"attributeTypes":{},
"cellAttributeTypes":{},
"valueType":{
"type":"string"
}
},
{
"name":"value",
"attributeTypes":{},
"cellAttributeTypes":{},
"valueType":{
"type":"string"
}
}
]
},
"result":{
"attributes":{},
"columnAttributes":[
{},
{}
],
"rows":[
{
"values":[
{
"value":"save_back_color"
},
{
"value":"black"
}
],
"domainId":0,
"entityIds":{
"name":{
"id":3
}
}
},
{
"values":[
{
"value":"required_back_color"
},
{
"value":"#F0E5FF"
}
],
"domainId":0,
"entityIds":{
"name":{
"id":5
}
}
},
...
]
}
}
Все операции с базой проводятся через транзакции, которые исполняются полностью и атомарно.
Запрос: POST /transaction
В качестве тела запроса передаётся объект ITransaction
. В случае успешного выполнения транзакции возвращается объект типа ITransactionResult
.
Следующие типы на языке TypeScript описывают формат отправляемых и возвращаемых данных:
export interface IInsertEntityOp {
type: "insert";
entity: IEntityRef;
entries: Record<FieldName, any>;
}
export interface IUpdateEntityOp {
type: "update";
entity: IEntityRef;
id: number;
entries: Record<FieldName, any>;
}
export interface IDeleteEntityOp {
type: "delete";
entity: IEntityRef;
id: number;
}
export type TransactionOp = IInsertEntityOp | IUpdateEntityOp | IDeleteEntityOp;
export interface ITransaction {
operations: TransactionOp[];
}
export interface IInsertEntityResult {
type: "insert";
id: number;
}
export interface IUpdateEntityResult {
type: "update";
}
export interface IDeleteEntityResult {
type: "delete";
}
export type TransactionOpResult = IInsertEntityResult | IUpdateEntityResult | IDeleteEntityResult;
export interface ITransactionResult {
results: TransactionOpResult[];
}
Например, операция добавления новой записи в таблицу user,test
с полем foo
, установленным в 123
:
POST /transaction
Authorization: Bearer ВАШ_ТОКЕН
Content-type: application/json
{
"operations":[
{
"type":"insert",
"entity":{
"schema":"user",
"name":"test"
},
"entries":{
"foo":123
}
}
]
}
Результат запроса:
{
"results":[
{
"type":"insert",
"id":120
}
]
}
Запрос: POST /actions/СХЕМА_ПРОЦЕДУРЫ/НАЗВАНИЕ_ПРОЦЕДУРЫ/run
POST /actions/marketing/create_list_from_selected_elements/run
Authorization: Bearer YOUR_OIDC_TOKEN
Content-type: application/json
{
"ids":[26,54,34],
"list_name":"Список клиентов"
}
Результат запроса:
В result
- все данные, возвращенные из процедуры:
{
"result": {
"ok": true
}
}
или
{
"result": {
"ref": {
"schema": "marketing",
"name": "list_form"
},
"args": {
"id": 7
},
"target": "modal"
}
}
Запрос: GET /check_access
GET /check_access
Authorization: Bearer YOUR_OIDC_TOKEN
Content-type: application/json
Python example draft:
response = requests.request(
"GET",
"https://" + instance_name + ".api.ozma.org/check_access"
headers={"Authorization": "Bearer REPLACE_WITH_YOUR_OIDC_TOKEN"},
json={},
)
Если ответ 404, то такого инстанса не существует.
Запрос: GET /layouts
GET /layouts
Authorization: Bearer YOUR_OIDC_TOKEN
Content-type: application/zip
PUT /layouts
Authorization: Bearer YOUR_OIDC_TOKEN
Content-type: application/zip
Python example draft:
# Download scheme
response = requests.request(
"GET",
"https://" + instance_name + ".api.ozma.org/layouts"
headers={
"Authorization": "Bearer REPLACE_WITH_YOUR_OIDC_TOKEN",
"Accept": "application/zip"
},
params = {"skip_preloaded":"1"},
)
# Upload cheme to new instance
response_allschemas = requests.request(
"PUT",
"https://" + instance_name + ".api.ozma.org/layouts"
headers={
"Authorization": "Bearer REPLACE_WITH_YOUR_OIDC_TOKEN",
"Accept": "application/zip"
},
data=response_allschemas.content,
)