%%%------------------------------------------------------------------- %%% @doc Административный обработчик списка администраторов. %%% GET – список всех администраторов (доступен всем администраторам). %%% POST – создать нового администратора (только для superadmin). %%% @end %%%------------------------------------------------------------------- -module(admin_handler_admins). -behaviour(cowboy_handler). -export([init/2]). -export([trails/0]). -include("records.hrl"). %%% cowboy_handler callback -spec init(cowboy_req:req(), any()) -> {ok, cowboy_req:req(), any()}. init(Req, _Opts) -> case cowboy_req:method(Req) of <<"GET">> -> list_admins(Req); <<"POST">> -> create_admin(Req); _ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>) end. %%% Swagger metadata -spec trails() -> [map()]. trails() -> ListGet = #{ path => <<"/v1/admin/admins">>, method => <<"GET">>, description => <<"List all admins (any admin)">>, tags => [<<"Admins">>], parameters => [ #{name => <<"role">>, in => <<"query">>, schema => #{type => string}}, #{name => <<"status">>, in => <<"query">>, schema => #{type => string}}, #{name => <<"q">>, in => <<"query">>, schema => #{type => string}, description => <<"Search by email or nickname">>}, #{name => <<"sort">>, in => <<"query">>, schema => #{type => string, enum => [<<"email">>, <<"role">>, <<"created_at">>]}, description => <<"Sort field">>}, #{name => <<"order">>, in => <<"query">>, schema => #{type => string, enum => [<<"asc">>, <<"desc">>]}, description => <<"Sort order">>}, #{name => <<"limit">>, in => <<"query">>, schema => #{type => integer}}, #{name => <<"offset">>, in => <<"query">>, schema => #{type => integer}} ], responses => #{ 200 => #{ description => <<"Array of admins">>, content => #{<<"application/json">> => #{schema => #{ type => array, items => admin_schema() }}} } } }, PostCreate = #{ path => <<"/v1/admin/admins">>, method => <<"POST">>, description => <<"Create a new admin (superadmin only)">>, tags => [<<"Admins">>], requestBody => #{ required => true, content => #{<<"application/json">> => #{schema => #{ type => object, required => [<<"email">>, <<"password">>, <<"role">>], properties => #{ email => #{type => string, format => <<"email">>}, password => #{type => string}, role => #{type => string, enum => [<<"superadmin">>, <<"admin">>, <<"moderator">>, <<"support">>]} } }}} }, responses => #{ 201 => #{description => <<"Admin created">>}, 400 => #{description => <<"Invalid fields">>}, 403 => #{description => <<"Only superadmin can create admins">>}, 409 => #{description => <<"Email already exists">>} } }, [ListGet, PostCreate]. -spec admin_schema() -> map(). admin_schema() -> #{ type => object, properties => #{ id => #{type => string}, email => #{type => string, format => <<"email">>}, role => #{type => string, enum => [<<"superadmin">>, <<"admin">>, <<"moderator">>, <<"support">>]}, status => #{type => string, enum => [<<"active">>, <<"blocked">>]}, nickname => #{type => string, nullable => true}, avatar_url => #{type => string, nullable => true}, timezone => #{type => string, nullable => true}, language => #{type => string, nullable => true}, phone => #{type => string, nullable => true}, preferences => #{type => object, nullable => true}, last_login => #{type => string, format => <<"date-time">>}, created_at => #{type => string, format => <<"date-time">>}, updated_at => #{type => string, format => <<"date-time">>} } }. %%%=================================================================== %%% HTTP-методы %%%=================================================================== %% @doc GET /v1/admin/admins – список администраторов (доступен всем администраторам). -spec list_admins(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}. list_admins(Req) -> case handler_utils:auth_admin(Req) of {ok, _AdminId, Req1} -> Filters = parse_admin_filters(Req1), Pagination = handler_utils:parse_pagination_params(Req1), {ok, Total, Admins} = logic_admin:list_admins(Filters, Pagination), Json = [handler_utils:admin_to_json(A) || A <- Admins], ExtraHeaders = handler_utils:pagination_headers(Pagination, Total), handler_utils:send_json(Req1, 200, Json, ExtraHeaders); {error, Code, Msg, Req1} -> handler_utils:send_error(Req1, Code, Msg) end. %% @doc POST /v1/admin/admins – создание администратора (только суперадмин). -spec create_admin(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}. create_admin(Req) -> case handler_utils:is_superadmin(Req) of {ok, SuperAdminId, Req1} -> {ok, Body, Req2} = cowboy_req:read_body(Req1), try jsx:decode(Body, [return_maps]) of #{<<"email">> := Email, <<"password">> := Password, <<"role">> := RoleBin} -> Role = try binary_to_existing_atom(RoleBin, utf8) catch error:badarg -> undefined end, case Role of undefined -> handler_utils:send_error(Req2, 400, <<"Invalid role">>); _ -> case logic_admin:create_admin(Email, Password, Role) of {ok, Admin} -> % Аудит создания администратора admin_utils:log_admin_action(SuperAdminId, <<"create_admin">>, <<"admin">>, Email, Req2), handler_utils:send_json(Req2, 201, handler_utils:admin_to_json(Admin)); {error, email_exists} -> handler_utils:send_error(Req2, 409, <<"Email already exists">>); {error, invalid_role} -> handler_utils:send_error(Req2, 400, <<"Invalid role">>); {error, _} -> handler_utils:send_error(Req2, 500, <<"Internal server error">>) end end; _ -> handler_utils:send_error(Req2, 400, <<"Missing fields">>) catch _:_ -> handler_utils:send_error(Req1, 400, <<"Invalid JSON">>) end; {error, Code, Msg, Req1} -> handler_utils:send_error(Req1, Code, Msg) end. %%%=================================================================== %%% Вспомогательные функции %%%=================================================================== %% @private Разбор query-параметров для фильтрации. -spec parse_admin_filters(cowboy_req:req()) -> map(). parse_admin_filters(Req) -> Qs = cowboy_req:parse_qs(Req), #{ role => to_atom(proplists:get_value(<<"role">>, Qs)), status => to_atom(proplists:get_value(<<"status">>, Qs)), q => proplists:get_value(<<"q">>, Qs) }. %% @private Безопасное преобразование бинарного значения в атом. to_atom(undefined) -> undefined; to_atom(Bin) when is_binary(Bin) -> try binary_to_existing_atom(Bin, utf8) catch error:badarg -> Bin end.