Урок 25. Создание API-запросов
В этом уроке мы разберем, как создавать кастомные API-запросы, которые позволят сторонним сервисам взаимодействовать с нашим сервером.
Если хотите начать практику с этого урока, то вам необходимо развернуть учебный проект по инструкции в статье Разворачивание проекта.
При разворачивании проекта используйте backup базы данных, который можете найти в архиве из раздела Ответы прошлого урока. Скопируйте папки Forms, Workflow и Patterns в папку с развернутым проектом, например, в папку D:\WT\Projects\Template\Projects\1. Template.
Инструкция по подключению шаблонов находится по ссылке.
Создание пользователя
Все кастомные API-запросы, которые отправляются на сервер из мобильных приложений или с сайта, должны быть подписаны токеном, который создается при авторизации пользователя в программе.
Для начала добавим специального пользователя "Пользователь для сайта", под которым будет авторизовываться сторонний ресурс для доступа к API:
DO
$BODY$
DECLARE
_permission_block_id smallint;
_permission_block_item_id smallint;
_permission_id smallint;
_group_id smallint;
_public_user_id smallint;
_user_id smallint;
BEGIN
-- Добавление разрешений
INSERT INTO template.permission_block(id_title, title)
VALUES ('site', 'Сайт')
ON CONFLICT (id_title) DO NOTHING
RETURNING permission_block_id INTO _permission_block_id;
INSERT INTO template.permission_block_item(permission_block_id, id_title, title)
VALUES (_permission_block_id, 'site_api', 'API-запросы для сайта')
ON CONFLICT (id_title) DO NOTHING
RETURNING permission_block_item_id INTO _permission_block_item_id;
INSERT INTO template.permission(name, permission_block_item_id)
VALUES ('SiteApiMethodPermission', _permission_block_item_id)
ON CONFLICT (name) DO NOTHING
RETURNING permission_id INTO _permission_id;
INSERT INTO public.strings (strings_id, language_id, text_value)
VALUES
('site', 1, 'Сайт'),
('site', 2, 'Site'),
('site_api', 1, 'API-запросы для сайта'),
('site_api', 2, 'Website API requests');
-- Добавление разрешений
-- Создание группы
INSERT INTO template.group (name, title, description)
VALUES ('UserForSiteGroup', 'Пользователи для сайта', 'Группа пользователей для работы с сайтом')
RETURNING group_id INTO _group_id;
INSERT INTO template.group_permission(group_id, permission_id)
VALUES (_group_id, _permission_id)
ON CONFLICT (group_id, permission_id) DO NOTHING;
-- Создание группы
-- Создание пользователя
CREATE EXTENSION pgcrypto;
INSERT INTO public.user(user_name, user_full_name, user_password, person)
VALUES('USER_FOR_SITE', 'Пользователь для сайта', encode(digest('123', 'sha512'), 'hex'), false)
RETURNING user_id INTO _public_user_id;
INSERT INTO template.user(public_user_id)
VALUES(_public_user_id)
RETURNING user_id INTO _user_id;
INSERT INTO template.user_group(user_id, group_id)
VALUES(_user_id, _group_id);
DROP EXTENSION pgcrypto;
-- Создание пользователя
END;
$BODY$;
Для пользователя создали группу, которой сразу назначили права доступа до SiteApiMethodPermission. Позже создадим это разрешение в серверном xml-файле.
Из запроса на получение списка групп пользователей необходимо исключить группу "Пользователи для сайта", чтобы нельзя было редактировать ее и выбирать при создании/редактировании пользователей.
Необходимо исключить блок "Сайт" из списка редактирования прав доступа для групп пользователей (PermissionBlockItemSelectSqlQuery).
Аутентификация
Запрос аутентификации
Для авторизации и получения токена используется POST-запрос /api/v1/tokens/signin.
Давайте создадим такой запрос в Postman:

Где localhost:49707 - IP-адрес и порт сервера.
Тело запроса:
{
"UserName":"USER_FOR_SITE",
"PasswordHash":"3c9909afec25354d551dae21590bb26e38d53f2173b8d3dc3eee4c047e7ab1c1eb8b85103e3be7ba613b31bb5c9c36214dc9f14a42fd7a2fdb84856bca5c44c2",
"LongToken":true
}
В теле запроса указаны три поля:
UserName - логин пользователя. Обязательное поле. Ожидается строковое значение;
PasswordHash - хэш пароля (Sha512). Обязательное поле. Ожидается строковое значение;
LongToken - признак использования токена с большим временем жизни. Необязательное поле. Ожидается логическое значение. По умолчанию используется значение False.
Если LongToken имеет значение false, то время жизни токена равно 10 минутам. Если true, то 15 годам.
В ответ от сервера мы получим:

В ответе поля:
accessToken - основной токен доступа. Им необходимо подписывать все запросы;
refreshToken - токен, необходимый для обновления основного токена;
expires - срок жизни (точнее, срок годности) accessToken, указанный в секундах с 1970-01-01.
Запрос на обновление токена
Для обновления токена используется POST-запрос /api/v1/tokens/refresh:

Тело запроса:
{
"Token":"c7e078ba9d6c031ade9520b1cae90ccb33a1b0c87d964e6ad648ae938239d04aba39ae44828927bb30e2e655ca4d72acf10e95ccf973601afdd2a3981d4b2584"
}
В поле Token указывается refreshToken, полученный в POST-запросе /api/v1/tokens/signin.
Ответ от сервера буде содержать те же поля, что и в запросе на аутентификацию:

Отлично, теперь можем приступить к созданию кастомных API-запросов.
API-запросы
Перейдем в серверный xml-файл (Template.xml) и перед тэгом <Scheduler>
добавим тэг <ApiMethods>
, в котором будем описывать кастомные запросы.
GET-запрос
Создадим API-запрос на получение списка единиц измерения:
<ApiMethod Name="GetUnitListApiMethod" Route="unit_list" Method="Get" VersionCode="1">
<Response>
<Objects>
<Object Name="units" Array="True">
<Command Name="UnitListSelectSqlQueryCommand" />
</Object>
</Objects>
</Response>
</ApiMethod>
В атрибуте Route
тэга <ApiMethod>
указываем конечную точку маршрута, по которому будет выполняться действие, описанное в ApiMethod. В нашем случае полный маршрут будет иметь вид: http://localhost:50707/data_api/unit_list.
Необязательный тэг <Response>
описывает дополнительные поля в ответе на запрос. Таким образом, помимо системного поля result_code, будет добавлено поле units, содержащее массив объектов. За то, что поле будет возвращать массив, отвечает необязательный атрибут Array
тэга <Object>
. В качестве значения поля units будет подставляться результат выполнения команды UnitListSelectSqlQueryCommand типа SqlQueryCommand:
<Command Name="UnitListSelectSqlQueryCommand" Type="SqlQueryCommand">
<SqlQuery>
<Text>
SELECT
unit.unit_id,
unit.title,
short_title,
unit.archive
FROM
template.unit
ORDER BY unit.title;
</Text>
</SqlQuery>
</Command>
Добавим API-метод GetUnitListApiMethod в разрешение SiteApiMethodPermission, которое ранее добавили для группы "Пользователи для сайта":
<Permission Name="SiteApiMethodPermission" Type="ApiMethodPermission">
<ApiMethods>
<ApiMethod Name="GetUnitListApiMethod" />
</ApiMethods>
</Permission>
Давайте перейдем в Postman и создадим запрос localhost:49707/data_api/unit_list для проверки метода:

Все запросы должны быть подписаны полученным при аутентификации jwt-токеном, поэтому в заголовок запроса добавили параметр Authorization со значением: Bearer <accessToken>.
В ответ на запрос сервер вернет json-объект:

GET-запрос с двумя объектами
Создадим команды для получения списков категорий ТМЦ и самих ТМЦ:
<Command Name="MaterialCategorySelectSqlQueryCommand" Type="SqlQueryCommand">
<SqlQuery>
<Text>
SELECT
MC.material_category_id,
MC.parent_material_category_id,
MC.title
FROM
template.material_category MC
WHERE
NOT MC.archive
ORDER BY MC.title, MC.material_category_id;
</Text>
</SqlQuery>
</Command>
<Command Name="MaterialSelectSqlQueryCommand" Type="SqlQueryCommand">
<SqlQuery>
<Text>
SELECT
M.material_id,
M.material_category_id,
M.title,
M.unit_id,
M.unit_price
FROM
template.material M
WHERE
NOT M.archive
ORDER BY M.title;
</Text>
</SqlQuery>
</Command>
Добавим API-метод для получения этих списков:
<ApiMethod Name="GetMaterialListApiMethod" Route="material_list" Method="Get" VersionCode="1">
<Response>
<Objects>
<Object Name="material_category" Array="True">
<Command Name="MaterialCategorySelectSqlQueryCommand" />
</Object>
<Object Name="material" Array="True">
<Command Name="MaterialSelectSqlQueryCommand" />
</Object>
</Objects>
</Response>
</ApiMethod>
В Postman создадим и выполним запрос localhost:49707/data_api/material_list:

В ответ на запрос сервер вернет json-объект:

В ответе мы видим два поля: material_category и material с массивами объектов.
GET-запрос с параметрами
Давайте создадим API-запрос, который по идентификатору клиента будет возвращать список его заказов.
Создадим команду:
<Command Name="OrdersByClientIdSelectSqlQueryCommand" Type="SqlQueryCommand">
<SqlQuery>
<Text>
WITH _order AS (
SELECT
O.order_id,
O.client_id,
O.order_number,
O.order_date,
O.description
FROM
template.order O
WHERE
O.client_id = {client_id}
), _order_position AS (
SELECT
O.order_id,
SUM(OP.quantity * OP.unit_price) AS total_cost
FROM
_order O
JOIN template.order_position OP USING(order_id)
LEFT JOIN template.material M USING(material_id)
GROUP BY O.order_id
)
SELECT
O.order_id,
O.order_number,
O.order_date,
COALESCE(OP.total_cost, 0) AS total_cost
FROM
_order O
LEFT JOIN _order_position OP USING(order_id)
ORDER BY O.order_number ASC;
</Text>
</SqlQuery>
</Command>
В тексте запроса используется переменная client_id. Вместо этой переменной будет подставляться значение параметра, полученного в HTTP-запросе.
Добавим ApiMethod:
<ApiMethod Name="GetOrderListByClientIdApiMethod" Route="order_list" Method="Get" VersionCode="1">
<Parameters>
<Parameter Name="client_id" Type="Integer" Source="FromQuery" Required="True" />
</Parameters>
<Response>
<Objects>
<Object Name="orders" Array="True">
<Command Name="OrderByClientIdSelectSqlQueryCommand" />
</Object>
</Objects>
</Response>
</ApiMethod>
В тэге <Parameters>
можем перечислять все входные параметры. Для каждого параметра необходимо указать его тип и источник. В нашем случае для GET-запроса будем ожидать параметр client_id в строке запроса. Через атрибут Required
укажем, что параметр является обязательным.
Выполним в Postman запрос localhost:49707/data_api/order_list?client_id=2:

В ответ на запрос сервер вернет json-объект:

Так как параметру client_id указали источник FromQuery (значение атрибута Source), то контроллер будет искать этот параметр в строке запроса.
Давайте расширим этот запрос, добавив в него список позиций заказа:
<Command Name="OrderPositionsByClientIdSelectSqlQueryCommand" Type="SqlQueryCommand">
<SqlQuery>
<Text>
WITH _order AS (
SELECT
O.order_id,
O.client_id,
O.order_number,
O.order_date,
O.description
FROM
template.order O
WHERE
O.client_id = {client_id}
)
SELECT
O.order_id,
OP.order_position_id,
OP.material_id,
OP.quantity,
OP.unit_price
FROM
_order O
JOIN template.order_position OP USING(order_id)
ORDER BY OP.order_position_id;
</Text>
</SqlQuery>
</Command>
Мы можем просто добавить список позиций заказа отдельным полем:
<ApiMethod Name="OrderListByClientIdApiMethod" Route="order_list" Method="Get" VersionCode="1">
<Parameters>
<Parameter Name="client_id" Type="Integer" Source="FromQuery" Required="True" />
</Parameters>
<Response>
<Objects>
<Object Name="orders" Array="True">
<Command Name="OrdersByClientIdSelectSqlQueryCommand" />
</Object>
<Object Name="order_position" Array="True">
<Command Name="OrderPositionsByClientIdSelectSqlQueryCommand" />
</Object>
</Objects>
</Response>
</ApiMethod>
А можем, используя тэг <Relations>
, описать отношения полей друг другу:
<ApiMethod Name="OrderListByClientIdApiMethod" Route="order_list" Method="Get" VersionCode="1">
<Parameters>
<Parameter Name="client_id" Type="Integer" Source="FromQuery" Required="True" />
</Parameters>
<Response>
<Objects>
<Object Name="orders" Array="True">
<Command Name="OrdersByClientIdSelectSqlQueryCommand" />
</Object>
<Object Name="order_position" Array="True">
<Command Name="OrderPositionsByClientIdSelectSqlQueryCommand" />
</Object>
</Objects>
<Relations>
<Relation ParentObject="orders" ParentField="order_id" ChildObject="order_position" ChildField="order_id"></Relation>
</Relations>
</Response>
</ApiMethod>
В тэге <Relation>
указываем имена родительского и дочернего объектов и их поля, по которым будет строиться дерево отношений.
Выполним тестовый запрос localhost:49707/data_api/order_list?client_id=2:

В ответ на запрос сервер вернет json-объект:

В ответе от сервера каждый объект заказа имеет массив позиций заказа.
Можно указывать несколько отношений. Добавьте команду на получение списка оплат в заказах для клиента и добавьте их в OrderListByClientIdApiMethod. Укажите в тэге <Relation>
отношение списка оплат к списку заказов.

POST-запрос
В качестве примера POST-запроса создадим API-метод на добавление оплаты в заказ.
Для начала добавим в таблицу template.account колонку for_site для обозначения счета, на который будут привязываться оплаты с сайта, и добавим такой счет:
ALTER TABLE template.account
ADD COLUMN for_site boolean NOT NULL DEFAULT false;
INSERT INTO template.account (title, cash, for_site)
VALUES ('Счет для оплат с сайта', false, true);
Создадим функцию для добавления оплаты через API-метод:
CREATE OR REPLACE FUNCTION template.order_payment_insert(
IN in_order_id bigint,
IN in_summ numeric)
RETURNS TABLE(order_payment_id bigint, error_message character varying) AS
$BODY$
DECLARE
_account_id bigint;
_cash_id bigint;
_order_payment_id bigint;
_error_message varchar;
_rec record;
BEGIN
-- Проверка входящих параметров
SELECT * INTO _rec FROM template.order WHERE order_id = in_order_id;
_account_id = account_id FROM template.account WHERE for_site LIMIT 1;
IF (_rec ISNULL) THEN
_error_message = 'no_order';
ELSIF (_rec.deleted) THEN
_error_message = 'order deleted';
ELSIF (_account_id ISNULL) THEN
_error_message = 'no_account';
END IF;
IF (_error_message != '') THEN
RETURN QUERY SELECT _order_payment_id, _error_message;
RETURN;
END IF;
-- Проверка входящих параметров
INSERT INTO template.cash(
cash_date,
account_id,
operation_id,
summ
)
SELECT
now(),
_account_id,
O.operation_id,
in_summ
FROM
template.operation O
WHERE
id_title = 'OrderPaymentOperation'
RETURNING cash_id INTO _cash_id;
INSERT INTO template.order_payment(
order_id,
cash_id
)
VALUES(
in_order_id,
_cash_id
)
RETURNING order_payment.order_payment_id INTO _order_payment_id;
RETURN QUERY SELECT _order_payment_id, _error_message;
END;
$BODY$
LANGUAGE plpgsql;
Функция будет возвращать идентификатор добавленной записи, либо ошибку, если передан неверный идентификатор заказа, заказ удален или отсутствует счет для оплат с сайта.
Создадим команду для вызова функции:
<Command Name="CreateOrderPaymentInsertSqlQueryCommand" Type="SqlQueryCommand">
<SqlQuery>
<Text>
SELECT
order_payment_id,
error_message
FROM
template.order_payment_insert({order_id}::bigint, {summ}::numeric);
</Text>
</SqlQuery>
</Command>
Функция template.order_payment_insert() принимает два параметра, которые должны быть обязательными для запроса на добавления оплаты в заказ.
Создадим ApiMethod, который будет выполнять команду:
<ApiMethod Name="AddOrderPaymentApiMethod" Route="add_order_payment" Method="Post" VersionCode="1">
<Parameters>
<Parameter Name="order_id" Type="Integer" Source="FromBody" Required="True" />
<Parameter Name="summ" Type="Decimal" Source="FromBody" Required="True" />
</Parameters>
<Commands>
<Command Name="CreateOrderPaymentInsertSqlQueryCommand" />
</Commands>
<Response>
<Objects>
<Object Name="order_payment_id">
<Command Name="CreateOrderPaymentInsertSqlQueryCommand" Parameter="order_payment_id" />
</Object>
<Object Name="error_message">
<Command Name="CreateOrderPaymentInsertSqlQueryCommand" Parameter="error_message" />
</Object>
</Objects>
</Response>
</ApiMethod>
В тэге <Commands>
можем описать последовательность команд, а в тэге <Response>
обращаться к результату выполнения команд. При этом мы можем через атрибут Parameter обратиться к конкретному значению в результате команды.
Выполним тестовый запрос localhost:49707/data_api/add_order_payment:

В ответ на запрос сервер вернет json-объект:

Кастомные типы параметров
Давайте создадим API-метод на сохранение заказа одним запросом. Т.е. помимо основной информации о заказе будем принимать и сохранять позиции заказа. У нас будет один метод на создание нового заказа и на редактирование существующего.
Первым делом в Postman создадим запрос localhost:49707/data_api/save_order:

Запрос будет передавать json-объект вида:
{
"client_id":8,
"order":{
"order_id":null,
"order_date":"2023-12-21 11:00:28Z",
"description":"Тестовый заказ с сайта",
"order_position":[
{
"order_position_id":null,
"material_id":4,
"quantity":5,
"unit_price":200
}
]
}
}
Для источника FromBody есть ограничение на обработку параметров относительно уровня вложенности объектов. В качестве параметров запроса можем указывать только поля основного объекта, которые принимают значение простых типов (строка, число, дата, логическое) и массив простых типов. Мы можем напрямую обратиться к полю client_id и получить его значение, но не можем обратиться к полям объекта order. Поэтому в ApiMethod мы укажем параметр order с типом Json:
<ApiMethod Name="SaveOrderApiMethod" Route="save_order" Method="Put" VersionCode="1">
<Enabled>
<Condition Name="SiteModuleIsOnSqlQueryCondition" />
</Enabled>
<Parameters>
<Parameter Name="client_id" Type="Integer" Source="FromBody" Required="False" />
<Parameter Name="order" Type="Json" Source="FromBody" Required="False" />
</Parameters>
<Commands>
<Command Name="SaveOrderSqlQueryCommand" />
</Commands>
<Response>
<Objects>
<Object Name="order_id">
<Command Name="SaveOrderSqlQueryCommand" Parameter="order_id" />
</Object>
<Object Name="order_number">
<Command Name="SaveOrderSqlQueryCommand" Parameter="order_number" />
</Object>
</Objects>
</Response>
</ApiMethod>
Следовательно, в команде SaveOrderSqlQueryCommand с параметром order будем работать как с json-строкой:
<Command Name="SaveOrderSqlQueryCommand" Type="SqlQueryCommand">
<SqlQuery>
<Text>
SELECT
order_id,
order_number
FROM
template.save_order({client_id}::bigint, {order}::json);
</Text>
</SqlQuery>
</Command>
Функция сохранения заказа будет иметь вид:
CREATE OR REPLACE FUNCTION template.save_order(
in_client_id bigint,
in_order json)
RETURNS TABLE(order_id bigint, order_number character varying) AS
$BODY$
DECLARE
_order_id bigint = in_order ->> 'order_id';
_order_date timestamp = convert_date_json((in_order ->> 'order_date')::timestamp);
_description varchar = in_order->> 'description';
_order_number varchar = lpad(floor(random() * 300)::varchar, 5, '0');
_order_position record;
BEGIN
IF (_order_id ISNULL) THEN
INSERT INTO template.order (
order_number,
order_date,
client_id,
description,
added
)
VALUES (
_order_number,
_order_date,
in_client_id,
_description,
true
)
RETURNING "order".order_id INTO _order_id;
ELSE
UPDATE template.order
SET
order_number = _order_number,
order_date = _order_date,
client_id = in_client_id,
description = _description
WHERE
order_id = _order_id;
END IF;
FOR _order_position IN (
SELECT
*
FROM
json_to_recordset(in_order -> 'order_position') AS T(
order_position_id bigint,
material_id bigint,
quantity numeric,
unit_price numeric
)
)
LOOP
IF (_order_position.order_position_id ISNULL) THEN
INSERT INTO template.order_position (
order_id,
material_id,
quantity,
unit_price
)
VALUES (
_order_id,
_order_position.material_id,
_order_position.quantity,
_order_position.unit_price
);
ELSE
UPDATE template.order_position
SET
material_id = _order_position.material_id,
quantity = _order_position.quantity,
unit_price = _order_position.unit_price
WHERE
order_position_id = _order_position.order_position_id;
END IF;
END LOOP;
RETURN QUERY SELECT _order_id, _order_number;
END;
$BODY$
LANGUAGE plpgsql;
Обратите внимание: даты между клиентом и сервером передаются в UTC, и в JSON-объекте они не приводятся автоматически к часовому поясу сервера. Для корректного сохранения даты со временем используем функцию public.convert_date_json().
Выполним тестовый запрос:

POST-запрос с файлами
Чтобы в кастомные API-запросы передавать файлы, HTTP-запрос должен использовать тип содержимого multipart/form-data.
Перейдем в Postman и создадим запрос localhost:49707/data_api/save_client:

Запрос содержит два объекта формы:
client - json-объект с описанием данных клиента;
file - передаваемый файл.
Объект client будет иметь вид:
{
"client_id":null,
"city_id":3,
"date_of_birth":"1994-07-22",
"title":"Савушкин Степан Маркович",
"email":"test123@mail.ru",
"phone":"+79121234567"
}
Создадим ApiMethod для обработки такого запроса:
<ApiMethod Name="SaveClientApiMethod" Route="save_client" Method="Put" VersionCode="1">
<Parameters>
<Parameter Name="client" Type="Json" Source="FormData" Required="False" />
<Parameter Name="file" Type="File" Source="FormData" />
</Parameters>
<Commands>
<Command Name="SaveClientSqlQueryCommand" />
</Commands>
<Response>
<Objects>
<Object Name="client_id">
<Command Name="SaveClientSqlQueryCommand" Parameter="client_id"/>
</Object>
</Objects>
</Response>
</ApiMethod>
В качестве источника данных укажем FormData.
Если мы хотим передать на сервер массив файлов, то достаточно добавить в форму запроса еще один объект с тем же именем:

А в ApiMethod для параметра file добавить атрибут Array
со значением True. В противном случае в запрос будет подставляться информация о последнем файле.
Команда SaveClientSqlQueryCommand будет иметь вид:
<Command Name="SaveClientSqlQueryCommand" Type="SqlQueryCommand">
<SqlQuery>
<Text>
SELECT template.save_client({client}::json, {file}::json) AS client_id;
</Text>
</SqlQuery>
</Command>
Получив соответствующий запрос, сервер загрузит файл, сгенерирует guid и добавит информацию о нем в таблицу public.file. Для переменной file будет сгенерирована json-строка в виде массива объектов с двумя полями: file_name (имя файла) и guid (сгенерированный идентификатор).
Таким образом, текст запроса с замененными переменными будет иметь вид:
SELECT template.save_client('{
"client_id":null,
"city_id":3,
"date_of_birth":"1994-07-22",
"title":"Савушкин Степан Маркович",
"email":"test123@mail.ru",
"phone":"+79121234567"
}'::json, '[
{
"file_name": "ua1hNO_T_O8.jpg",
"guid": "70b3464d-2731-4aef-9f52-89caa992109b"
}
]'::json) AS client_id;
Создайте самостоятельно функцию template.save_client(json, json), которая будет создавать нового клиента или обновлять данные существующего. В качестве результата функция должна возвращать идентификатор клиента.
В ответ на запрос сервер вернет json-объект вида:

Работа с файлами
Загрузка файла
Для загрузки файла на сервер используется системный POST-запрос /files/upload, который в форме запроса должен иметь объект с именем file. После сохранения файла на сервере в ответе в поле FileGuid вернется сгенерированный guid.

Скачивание файла
Для скачивания файлов с сервера используется системный GET-запрос /files/download, ожидающий обязательный параметр fileGuid - guid файла, который генерируется автоматически при загрузке файла на сервер.
Запрос должен быть подписан токеном, полученным при авторизации пользователя в программе.

Блокировка API-запросов
Скорректируйте форму настроек, добавив объект CheckBox для включения/отключения модуля для работы с сайтом и кнопку для смены пароля у пользователя для сайта.

По кнопке "Изменить пароль..." должна открываться форма смены пароля (TemplateUserPasswordEdit.xml), где для пользователя "Пользователь для сайта" (user_name = 'USER_FOR_SITE') можно задать новый пароль. Измените самостоятельно логику смены пароля.
В таблице template.settings добавим колонку для включения/отключения модуля сайта:
ALTER TABLE template.settings
ADD COLUMN site_module_is_on boolean NOT NULL DEFAULT false;
Теперь в файле Template.xml можем создать условие для проверки активности модуля сайта:
<Condition Name="SiteModuleIsOnSqlQueryCondition" Type="SqlQueryCondition">
<Text>
SELECT site_module_is_on FROM template.settings;
</Text>
</Condition>
Добавим в GetUnitListApiMethod необязательный тэг <Enabled>
, в котором укажем новое условие:
<ApiMethod Name="GetUnitListApiMethod" Route="unit_list" Method="Get" VersionCode="1">
<Enabled>
<Condition Name="SiteModuleIsOnSqlQueryCondition" />
</Enabled>
<Response>
<Objects>
<Object Name="units" Array="True">
<Command Name="UnitListSelectSqlQueryCommand" />
</Object>
</Objects>
</Response>
</ApiMethod>
Сделайте то же самое для остальных методов.
Если API-метод недоступен, то в ответ на запрос будет возвращаться код 404 Not Found:

Итоги
В этом уроке мы познакомились с возможностью создавать кастомные API-запросы для предоставления доступа сторонним сервисам, таким как сайты или мобильные приложения.
Помимо кастомных API-запросов рассмотрели штатные запросы для аутентификации пользователей и для работы с файлами.
Как маленький бонус: узнали о возможностях PostgreSQL по работе с JSON - в следующем уроке рассмотрим, как можно на форме создавать json-объекты для отправки их на сервер.
Ответы
В архиве присутствуют xml-файлы форм и серверный xml-файл, также лежит бэкап базы данных и файл с запросами на изменение структуры базы данных - с помощью файлов можете проверить себя.
Ниже прикреплен экспортированный из Postman файл с коллекцией запросов:
Чтобы импортировать коллекцию из файла, необходимо кликнуть по кнопке Import, и в отрывшемся окне выбрать файл коллекции.

Last updated