%%%------------------------------------------------------------------- %%% @doc Административный обработчик для получения статистики. %%% GET – возвращает агрегированную статистику для дашборда. %%% Поддерживает фильтрацию по диапазону дат (from, to). %%% @end %%%------------------------------------------------------------------- -module(admin_handler_stats). -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">> -> get_stats(Req); _ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>) end. %%% Swagger metadata -spec trails() -> [map()]. trails() -> [ #{ path => <<"/v1/admin/stats">>, method => <<"GET">>, description => <<"Get admin dashboard statistics">>, tags => [<<"Statistics">>], parameters => [ #{name => <<"from">>, in => <<"query">>, schema => #{type => string, format => <<"date-time">>}, description => <<"Start date (ISO8601)">>}, #{name => <<"to">>, in => <<"query">>, schema => #{type => string, format => <<"date-time">>}, description => <<"End date (ISO8601)">>} ], responses => #{ 200 => #{ description => <<"Statistics object">>, content => #{<<"application/json">> => #{schema => #{ type => object, properties => stats_schema() }}} }, 403 => #{description => <<"Admin access required">>} } } ]. stats_schema() -> #{ <<"users">> => #{type => integer, description => <<"Total number of users">>}, <<"events">> => #{type => integer}, <<"reviews">> => #{type => integer}, <<"calendars">> => #{type => integer}, <<"reports">> => #{type => integer}, <<"tickets">> => #{type => integer}, <<"subscriptions">> => #{type => integer}, <<"active_subscriptions">> => #{type => integer} }. %%% Internal functions %% @doc Получить статистику с учетом параметров запроса. -spec get_stats(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}. get_stats(Req) -> case handler_utils:auth_admin(Req) of {ok, AdminId, Req1} -> {ok, Admin} = core_admin:get_by_id(AdminId), Role = Admin#admin.role, Stats = case parse_date_range(Req1) of {ok, From, To} -> logic_stats:get_stats(Role, AdminId, From, To); _ -> logic_stats:get_stats(Role, AdminId) end, handler_utils:send_json(Req1, 200, Stats); {error, Code, Message, Req1} -> handler_utils:send_error(Req1, Code, Message) end. %% @private Разбирает параметры 'from' и 'to' из строки запроса. %% В случае успеха возвращает {ok, FromDT, ToDT}. -spec parse_date_range(cowboy_req:req()) -> {ok, calendar:datetime(), calendar:datetime()} | error. parse_date_range(Req) -> Qs = cowboy_req:parse_qs(Req), From = proplists:get_value(<<"from">>, Qs), To = proplists:get_value(<<"to">>, Qs), case {From, To} of {undefined, _} -> error; {_, undefined} -> error; {F, T} -> try FromDT = iso8601_to_datetime(F), ToDT = iso8601_to_datetime(T), {ok, FromDT, ToDT} catch _:_ -> error end end. %% @private Преобразует бинарную строку ISO8601 в кортеж datetime(). -spec iso8601_to_datetime(binary()) -> calendar:datetime(). iso8601_to_datetime(Str) -> [Date, Time] = binary:split(Str, <<"T">>), [Y, M, D] = [binary_to_integer(X) || X <- binary:split(Date, <<"-">>, [global])], [H, Min, S] = [binary_to_integer(X) || X <- binary:split(Time, <<":">>, [global])], {{Y, M, D}, {H, Min, S}}.