Данная страница документация не завершена.
Актуально для версии 1.22.00.
...
Поскольку практически каждая функция администратора должна иметь CRUD-составляющую, то вот как это выглядит для /portal/articles
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
return [ # Создать статью { method => 'POST', path => '/portal/articles/', # Параметры до валидатора params => POST_PORTAL_ARTICLES, controller => 'Portal::Api::admin::Articles', endpoint => \&Portal::Api::admin::Articles::post_portal_articles, credentials => [ 'ADMIN', 'ADMINSID' ] }, # Получить статьи { method => 'GET', path => '/portal/articles/', controller => 'Portal::Api::admin::Articles', endpoint => \&Portal::Api::admin::Articles::get_portal_articles, credentials => [ 'ADMIN', 'ADMINSID' ] }, # Получить конкретную статью { method => 'GET', path => '/portal/articles/:id/', controller => 'Portal::Api::admin::Articles', endpoint => \&Portal::Api::admin::Articles::get_portal_articles_id, credentials => [ 'ADMIN', 'ADMINSID' ] }, # Изменить конкретную статью { method => 'PUT', path => '/portal/articles/:id/', controller => 'Portal::Api::admin::Articles', endpoint => \&Portal::Api::admin::Articles::put_portal_articles_id, credentials => [ 'ADMIN', 'ADMINSID' ] }, # Удалить конкретную статью { method => 'DELETE', path => '/portal/articles/:id/', controller => 'Portal::Api::admin::Articles', endpoint => \&Portal::Api::admin::Articles::delete_portal_articles_id, credentials => [ 'ADMIN', 'ADMINSID' ] }, ] |
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
#********************************************************** =head2 user_routes() - Returns available USER API paths =cut #********************************************************** sub user_routes { my $self = shift; return [ { method => 'GET', # Для USER API ОБЯЗАТЕЛЬНО начинаем абсолютный путь с /user/*. path => '/user/portal/menu/', # Подключаем "контроллер" для API /user/portal/* controller => 'Portal::Api::user::News', # Даём ссылку на функцию-эндпойнт контроллера endpoint => \&Portal::Api::user::News::get_user_portal_news, credentials => [ # Определяем нужные параметры для авторизации. # USER - авторизация по header, полученном с /user/login # USERSID - авторизация по cookie (в том числе для api_call) # PUBLIC - без авторизации # Тоесть, в данном случае путь может работать как и с авторизованными пользователями, так и нет. # Внутри хэндлера можно определять какой пользователь, об этом ниже. 'USER', 'USERSID', 'PUBLIC' ] }, ] } |
...
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
package Portal::Api::user::News; =head1 NAME User Portal # Рекомендуем в подах записывать к каким # группам эндпойнтов относится данный контроллер Endpoints: /user/portal/news* /user/portal/menu =cut use strict; use warnings FATAL => 'all'; use Control::Errors; # Импортируем объект Portal для работы с базой # он должен находиться в /usr/abills/Abills/mysql/Portal.pm use Portal; my Portal $Portal; my Control::Errors $Errors; #********************************************************** =head2 new($db, $admin, $conf) =cut #********************************************************** sub new { my ($class, $db, $admin, $conf, $attr) = @_; my $self = { db => $db, admin => $admin, conf => $conf, attr => $attr }; bless($self, $class); $Portal = Portal->new($db, $admin, $conf); # Определяем словарь ошибок, который нам пришёл выше $Errors = $self->{attr}->{Errors}; return $self; } # здесь определять пути 1; |
...
В целом, определение роутов для USER API ничем не отличается от ADMIN API, кроме одной важной детали - в $path_params при авторизации будет приходить uid.
Очень важная составляющая.
Code Block | ||||
---|---|---|---|---|
| ||||
#********************************************************** =head2 get_user_portal_news($path_params, $query_params) Endpoint GET /user/portal/news =cut #********************************************************** sub get_user_portal_news { my $self = shift; my ($path_params, $query_params) = @_; # Не обязательно писать всю логику прямо внутри эндпоинта, как в примере с ADMIN API # Вы можете делить логику в бизнес-функции, для сокращения использования. # Но для простоты понимания, с самого начала лучше писать всё в эндпоинтах return $self->_portal_menu({ # Если пользователь авторизован - в $path_params->{uid} будет UID пользователя. # Если нет - поле будет пустое. UID => $path_params->{uid} || '', DOMAIN_ID => $query_params->{DOMAIN_ID}, PORTAL_MENU_ID => $query_params->{PORTAL_MENU_ID}, MAIN_PAGE => $query_params->{MAIN_PAGE}, LIST => 1 }); } |
Практики
Поиск, сортировка, пагинация
Для правильной работы сортировки мы рекомендуем делать это не вручну, а с помощью search_former которые находятся внутри любого современного модуля.
Он выглядит приблизительно так, с точки зрения пути GET /portal/menus
...
Написание OpenAPI
Каждое API нужно описывать.
Поэтому, по нашему микрофреймворку над OpenAPI
- Файл Api/swagger/(admin|user)/paths.yaml - основа
- Папка Api/swagger/(admin|user)/paths - определения путей
- Папка Api/swagger/(admin|user)/schemas - определения схем
Файл user/paths.yaml:
Code Block | ||||
---|---|---|---|---|
| ||||
/user/portal/news:
$ref: "./paths/news.yaml" |
И в ./paths/news.yaml:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
get:
tags:
- portal
summary: Список новостей
responses:
200:
description: Успешное выполнение
content:
application/json:
schema:
$ref: "../schemas/news.yaml"
security:
- USERSID: [ ] |
schemas/news.yaml:
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
type: object
properties:
news:
type: array
items:
type: object
properties:
content:
type: string
example: При замовленні пакету «ABillS+» платіть X грн/міс замість X грн/міс. Підключайте ТВ-пакет «База+» і отримаєте в подарунок півроку користування послугою! Кожен місяць сплачуйте лише 50% вартості пакету. Абонплата складе всього X грн.
date:
type: string
format: date
example: 2023-01-23
id:
type: number
example: 4
importance:
type: number
example: 1
onMainPage:
type: number
example: 1
permalink:
type: string
example: special-promotion-50-2023
picture:
type: string
example: "http://192.168.99.2:9443/images/attach/portal/9700077.png"
shortDescription:
type: string
example: Тарифний план для всіх у сімї
title:
type: string
example: Акціний тариф візьми поки можеш
topicId:
type: number
example: 1
topics:
type: array
items:
type: object
properties:
id:
type: number
example: 1
name:
type: string
example: Акції від нас
url:
type: string
example: "https://demo.abills.net.ua:9443/" |
И в конце запускаем misc/api/generate_docs.pl, и проверяем bundle_user.yaml.
И поздравляем, вы полностью разработали и описали USER API для модуля.
Практики
Поиск, сортировка, пагинация
Для правильной работы сортировки мы рекомендуем делать это не вручну, а с помощью search_former которые находятся внутри любого современного модуля.
Он выглядит приблизительно так, с точки зрения пути GET /portal/menus
Code Block | ||||
---|---|---|---|---|
| ||||
#********************************************************** =head2 get_portal_menus($path_params, $query_params) Endpoint GET /portal/menus =cut #********************************************************** sub get_portal_menus { my $self = shift; my ($path_params, $query_params) = @_; # Проверяем, определяем стандартные параметры для вызова my %PARAMS = ( COLS_NAME => 1, PAGE_ROWS => $query_params->{PAGE_ROWS} ? $query_params->{PAGE_ROWS} : 25, SORT => $query_params->{SORT} ? $query_params->{SORT} : 1, PG => $query_params->{PG} ? $query_params->{PG} : 0, DESC => $query_params->{DESC}, ); foreach my $param (keys %{$query_params}) { $query_params->{$param} = ($query_params->{$param} || "$query_params->{$param}" eq '0') ? $query_params->{$param} : '_SHOW'; } my $list = $Portal->portal_menu_list({ ID => '_SHOW', NAME => '_SHOW', URL => '_SHOW', DATE => '_SHOW', STATUS => '_SHOW', # С помощью внутренней деструктуризации присваиваем %$query_params, # Оверрайдим параметры %PARAMS, }); return { list => $list, total => $Portal->{TOTAL} }; } |
И смотрим что происходит под капотом у portal_menu_list
Code Block | ||||
---|---|---|---|---|
| ||||
#********************************************************** =head2 function portal_menu_list() - get menu section list Arguments: $attr Returns: \@list - Examples: my $list = $Portal->portal_menu_list({COLS_NAME=>1}); =cut #********************************************************** sub portal_menu_list { my $self = shift; my ($attr) = @_; # Преопределяем параметры, если их нет my $SORT = ($attr->{SORT}) ? $attr->{SORT} : 1; my $DESC = ($attr->{DESC}) ? $attr->{DESC} : ''; my $PG = $attr->{PG} ? $attr->{PG} : 0; my $PAGE_ROWS = $attr->{PAGE_ROWS} ? $attr->{PAGE_ROWS} : 25; # Эта функция на основе пришедших параметров и паттерном формирует $WHERE clause. my $WHERE = $self->search_former($attr, [ [ 'ID', 'INT', 'pm.id', 1 ], [ 'NAME', 'STR', 'pm.name', 1 ], [ 'URL', 'STR', 'pm.url', 1 ], [ 'DATE', 'STR', 'DATE(pm.date) as date', 1 ], [ 'STATUS', 'INT', 'pm.status', 1 ], ], { WHERE => 1 }); $self->query( "SELECT $self->{SEARCH_FIELDS} pm.id FROM portal_menu pm $WHERE ORDER BY $SORT $DESC LIMIT $PG, $PAGE_ROWS;;", undef, $attr ); my $list = $self->{list} || []; # Берём общий count $self->query("SELECT COUNT(*) AS total FROM portal_menu pm $WHERE;", undef, { INFO => 1 } ); return $list || []; } |
...