142 lines
6.5 KiB
Erlang
142 lines
6.5 KiB
Erlang
%%%-------------------------------------------------------------------
|
|
%%% @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. |