%%%------------------------------------------------------------------- %%% @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 => stats_schema()}} }, 403 => #{description => <<"Admin access required">>} } } ]. stats_schema() -> #{ type => object, properties => #{ <<"users_total">> => #{type => integer, description => <<"Total number of users">>}, <<"pending_users_total">> => #{type => integer, description => <<"Number of users awaiting email verification">>}, <<"events_total">> => #{type => integer, description => <<"Total number of events">>}, <<"calendars_total">> => #{type => integer, description => <<"Total number of calendars">>}, <<"reviews_total">> => #{type => integer, description => <<"Total number of reviews">>}, <<"reports_total">> => #{type => integer, description => <<"Total number of reports (all statuses)">>}, <<"reports_pending">> => #{type => integer, description => <<"Number of pending reports">>}, <<"reports_reviewed">> => #{type => integer, description => <<"Number of reviewed reports">>}, <<"reports_dismissed">> => #{type => integer, description => <<"Number of dismissed reports">>}, <<"tickets_total">> => #{type => integer, description => <<"Total number of tickets">>}, <<"tickets_open">> => #{type => integer, description => <<"Number of open tickets">>}, <<"subscriptions_total">> => #{type => integer, description => <<"Total number of subscriptions">>}, <<"trial_subscriptions">> => #{type => integer, description => <<"Number of trial subscriptions">>}, <<"avg_ticket_resolution_h">> => #{type => number, format => float, description => <<"Average ticket resolution time in hours">>}, <<"registrations_by_day">> => #{ type => array, items => #{type => object, properties => #{<<"date">> => #{type => string}, <<"count">> => #{type => integer}}}, description => <<"Daily user registrations">> }, <<"events_by_day">> => #{ type => array, items => #{type => object, properties => #{<<"date">> => #{type => string}, <<"count">> => #{type => integer}}}, description => <<"Daily event creations">> }, <<"reviews_by_day">> => #{ type => array, items => #{type => object, properties => #{<<"date">> => #{type => string}, <<"count">> => #{type => integer}}}, description => <<"Daily review creations">> }, <<"calendars_by_day">> => #{ type => array, items => #{type => object, properties => #{<<"date">> => #{type => string}, <<"count">> => #{type => integer}}}, description => <<"Daily calendar creations">> }, <<"reports_by_day">> => #{ type => array, items => #{type => object, properties => #{<<"date">> => #{type => string}, <<"count">> => #{type => integer}}}, description => <<"Daily report creations">> }, <<"tickets_by_day">> => #{ type => array, items => #{type => object, properties => #{<<"date">> => #{type => string}, <<"count">> => #{type => integer}}}, description => <<"Daily ticket creations">> }, <<"subscriptions_by_day">> => #{ type => array, items => #{type => object, properties => #{<<"date">> => #{type => string}, <<"count">> => #{type => integer}}}, description => <<"Daily subscription creations">> }, <<"pending_users_by_day">> => #{ type => array, items => #{type => object, properties => #{<<"date">> => #{type => string}, <<"count">> => #{type => integer}}}, description => <<"Daily pending user registrations">> }, <<"admin_activity">> => #{ type => array, items => #{type => object}, description => <<"Recent admin actions (superadmin only)">> } } }. %%% 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 = handler_utils:parse_datetime_qs(proplists:get_value(<<"from">>, Qs)), To = handler_utils:parse_datetime_qs(proplists:get_value(<<"to">>, Qs)), case {From, To} of {undefined, _} -> error; {_, undefined} -> error; {F, T} -> {ok, F, T} end.