Расширена статистика для разделов #22
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Административный обработчик статистики по календарям.
|
||||
%%% GET /v1/admin/calendars/stats
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_handler_calendar_stats).
|
||||
-behaviour(cowboy_handler).
|
||||
-export([init/2]).
|
||||
-export([trails/0]).
|
||||
|
||||
-include("records.hrl").
|
||||
|
||||
-spec init(cowboy_req:req(), any()) -> {ok, cowboy_req:req(), any()}.
|
||||
init(Req, _Opts) ->
|
||||
case cowboy_req:method(Req) of
|
||||
<<"GET">> -> get_calendar_stats(Req);
|
||||
_ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>)
|
||||
end.
|
||||
|
||||
-spec trails() -> [map()].
|
||||
trails() ->
|
||||
[ #{
|
||||
path => <<"/v1/admin/calendars/stats">>,
|
||||
method => <<"GET">>,
|
||||
description => <<"Calendar statistics">>,
|
||||
tags => [<<"Statistics">>],
|
||||
responses => #{
|
||||
200 => #{
|
||||
description => <<"Calendar statistics object">>,
|
||||
content => #{<<"application/json">> => #{schema => calendar_stats_schema()}}
|
||||
}
|
||||
}
|
||||
} ].
|
||||
|
||||
calendar_stats_schema() ->
|
||||
#{
|
||||
type => object,
|
||||
properties => #{
|
||||
<<"total_calendars">> => #{type => integer},
|
||||
<<"calendars_by_type">> => #{
|
||||
type => object,
|
||||
additionalProperties => #{type => integer}
|
||||
},
|
||||
<<"calendars_by_status">> => #{
|
||||
type => object,
|
||||
additionalProperties => #{type => integer}
|
||||
},
|
||||
<<"top_calendars_by_reviews">> => #{
|
||||
type => array,
|
||||
items => calendar_rank_schema()
|
||||
},
|
||||
<<"top_calendars_by_positive_reviews">> => #{
|
||||
type => array,
|
||||
items => calendar_rank_schema()
|
||||
},
|
||||
<<"top_calendars_by_negative_reviews">> => #{
|
||||
type => array,
|
||||
items => calendar_rank_schema()
|
||||
},
|
||||
<<"top_calendars_by_rating">> => #{
|
||||
type => array,
|
||||
items => calendar_rank_schema()
|
||||
}
|
||||
}
|
||||
}.
|
||||
|
||||
calendar_rank_schema() ->
|
||||
#{
|
||||
type => object,
|
||||
properties => #{
|
||||
<<"id">> => #{type => string},
|
||||
<<"title">> => #{type => string},
|
||||
<<"rating_avg">> => #{type => number, format => float},
|
||||
<<"review_count">> => #{type => integer}
|
||||
}
|
||||
}.
|
||||
|
||||
%%% Internal functions
|
||||
|
||||
get_calendar_stats(Req) ->
|
||||
case handler_utils:auth_admin(Req) of
|
||||
{ok, _AdminId, Req1} ->
|
||||
Total = core_calendar:count_calendars(),
|
||||
ByType = core_calendar:count_calendars_by_type(),
|
||||
ByStatus = core_calendar:count_calendars_by_status(),
|
||||
TopReviews = core_calendar:get_top_calendars_by_reviews(10),
|
||||
TopPositive = core_calendar:get_top_calendars_by_positive_reviews(10),
|
||||
TopNegative = core_calendar:get_top_calendars_by_negative_reviews(10),
|
||||
TopRating = core_calendar:get_top_calendars_by_rating(10),
|
||||
Stats = #{
|
||||
<<"total_calendars">> => Total,
|
||||
<<"calendars_by_type">> => maps:from_list([{atom_to_binary(T, utf8), C} || {T, C} <- ByType]),
|
||||
<<"calendars_by_status">> => maps:from_list([{atom_to_binary(S, utf8), C} || {S, C} <- ByStatus]),
|
||||
<<"top_calendars_by_reviews">> => format_calendar_list(TopReviews),
|
||||
<<"top_calendars_by_positive_reviews">> => format_calendar_list(TopPositive),
|
||||
<<"top_calendars_by_negative_reviews">> => format_calendar_list(TopNegative),
|
||||
<<"top_calendars_by_rating">> => format_calendar_list(TopRating)
|
||||
},
|
||||
handler_utils:send_json(Req1, 200, Stats);
|
||||
{error, Code, Msg, Req1} ->
|
||||
handler_utils:send_error(Req1, Code, Msg)
|
||||
end.
|
||||
|
||||
format_calendar_list(List) ->
|
||||
lists:map(fun(#calendar{id = Id, title = Title, rating_avg = RAvg}) ->
|
||||
#{<<"id">> => Id, <<"title">> => Title, <<"rating_avg">> => RAvg}
|
||||
end, List).
|
||||
@@ -0,0 +1,100 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Административный обработчик статистики по событиям.
|
||||
%%% GET /v1/admin/events/stats – возвращает общее количество,
|
||||
%%% распределение по типам, статусам и топ-10 по рейтингу.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_handler_event_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_event_stats(Req);
|
||||
_ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>)
|
||||
end.
|
||||
|
||||
%%% Swagger metadata
|
||||
-spec trails() -> [map()].
|
||||
trails() ->
|
||||
[
|
||||
#{
|
||||
path => <<"/v1/admin/events/stats">>,
|
||||
method => <<"GET">>,
|
||||
description => <<"Get detailed event statistics (total, by type, by status, top 10 by rating)">>,
|
||||
tags => [<<"Statistics">>],
|
||||
responses => #{
|
||||
200 => #{
|
||||
description => <<"Event statistics object">>,
|
||||
content => #{<<"application/json">> => #{schema => event_stats_schema()}}
|
||||
},
|
||||
403 => #{description => <<"Admin access required">>}
|
||||
}
|
||||
}
|
||||
].
|
||||
|
||||
event_stats_schema() ->
|
||||
#{
|
||||
type => object,
|
||||
properties => #{
|
||||
<<"total_events">> => #{type => integer, description => <<"Total number of events (master, active)">>},
|
||||
<<"events_by_type">> => #{
|
||||
type => object,
|
||||
description => <<"Number of events grouped by type (single, recurring)">>,
|
||||
additionalProperties => #{type => integer}
|
||||
},
|
||||
<<"events_by_status">> => #{
|
||||
type => object,
|
||||
description => <<"Number of events grouped by status (active, cancelled, completed)">>,
|
||||
additionalProperties => #{type => integer}
|
||||
},
|
||||
<<"top_events_by_rating">> => #{
|
||||
type => array,
|
||||
description => <<"Top 10 events by average rating">>,
|
||||
items => #{
|
||||
type => object,
|
||||
properties => #{
|
||||
<<"id">> => #{type => string},
|
||||
<<"title">> => #{type => string},
|
||||
<<"rating_avg">> => #{type => number, format => float},
|
||||
<<"rating_count">> => #{type => integer}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.
|
||||
|
||||
%%% Internal functions
|
||||
|
||||
%% @doc GET /v1/admin/events/stats – статистика по событиям.
|
||||
-spec get_event_stats(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}.
|
||||
get_event_stats(Req) ->
|
||||
case handler_utils:auth_admin(Req) of
|
||||
{ok, _AdminId, Req1} ->
|
||||
Total = core_event:count_events(),
|
||||
ByType = core_event:count_events_by_type(),
|
||||
ByStatus = core_event:count_events_by_status(),
|
||||
Top10 = core_event:get_top_events_by_rating(10),
|
||||
Stats = #{
|
||||
<<"total_events">> => Total,
|
||||
<<"events_by_type">> => maps:from_list([{atom_to_binary(Type, utf8), Count} || {Type, Count} <- ByType]),
|
||||
<<"events_by_status">> => maps:from_list([{atom_to_binary(Status, utf8), Count} || {Status, Count} <- ByStatus]),
|
||||
<<"top_events_by_rating">> => lists:map(fun event_to_map/1, Top10)
|
||||
},
|
||||
handler_utils:send_json(Req1, 200, Stats);
|
||||
{error, Code, Msg, Req1} ->
|
||||
handler_utils:send_error(Req1, Code, Msg)
|
||||
end.
|
||||
|
||||
event_to_map(Event) ->
|
||||
#{
|
||||
<<"id">> => Event#event.id,
|
||||
<<"title">> => Event#event.title,
|
||||
<<"rating_avg">> => Event#event.rating_avg,
|
||||
<<"rating_count">> => Event#event.rating_count
|
||||
}.
|
||||
@@ -0,0 +1,97 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Административный обработчик статистики по жалобам.
|
||||
%%% GET /v1/admin/reports/stats – возвращает общее количество,
|
||||
%%% распределение по типам цели, статусам и топ-10 целей по жалобам.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_handler_report_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_report_stats(Req);
|
||||
_ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>)
|
||||
end.
|
||||
|
||||
%%% Swagger metadata
|
||||
-spec trails() -> [map()].
|
||||
trails() ->
|
||||
[
|
||||
#{
|
||||
path => <<"/v1/admin/reports/stats">>,
|
||||
method => <<"GET">>,
|
||||
description => <<"Get detailed report statistics (total, by target type, by status, top 10 targets by reports)">>,
|
||||
tags => [<<"Statistics">>],
|
||||
responses => #{
|
||||
200 => #{
|
||||
description => <<"Report statistics object">>,
|
||||
content => #{<<"application/json">> => #{schema => report_stats_schema()}}
|
||||
},
|
||||
403 => #{description => <<"Admin access required">>}
|
||||
}
|
||||
}
|
||||
].
|
||||
|
||||
report_stats_schema() ->
|
||||
#{
|
||||
type => object,
|
||||
properties => #{
|
||||
<<"total_reports">> => #{type => integer, description => <<"Total number of reports">>},
|
||||
<<"reports_by_target_type">> => #{
|
||||
type => object,
|
||||
description => <<"Number of reports grouped by target type (event, calendar, review)">>,
|
||||
additionalProperties => #{type => integer}
|
||||
},
|
||||
<<"reports_by_status">> => #{
|
||||
type => object,
|
||||
description => <<"Number of reports grouped by status (pending, reviewed, dismissed)">>,
|
||||
additionalProperties => #{type => integer}
|
||||
},
|
||||
<<"top_targets_by_reports">> => #{
|
||||
type => array,
|
||||
description => <<"Top 10 targets by number of reports">>,
|
||||
items => #{
|
||||
type => object,
|
||||
properties => #{
|
||||
<<"target_type">> => #{type => string},
|
||||
<<"target_id">> => #{type => string},
|
||||
<<"report_count">> => #{type => integer}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.
|
||||
|
||||
%%% Internal functions
|
||||
|
||||
%% @doc GET /v1/admin/reports/stats – статистика по жалобам.
|
||||
-spec get_report_stats(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}.
|
||||
get_report_stats(Req) ->
|
||||
case handler_utils:auth_admin(Req) of
|
||||
{ok, _AdminId, Req1} ->
|
||||
Total = core_report:count_reports(),
|
||||
ByTargetType = core_report:count_reports_by_target_type(),
|
||||
ByStatus = core_report:count_reports_by_status(),
|
||||
Top10 = core_report:get_top_targets_by_reports(10),
|
||||
Stats = #{
|
||||
<<"total_reports">> => Total,
|
||||
<<"reports_by_target_type">> => maps:from_list([{atom_to_binary(Type, utf8), Count} || {Type, Count} <- ByTargetType]),
|
||||
<<"reports_by_status">> => maps:from_list([{atom_to_binary(Status, utf8), Count} || {Status, Count} <- ByStatus]),
|
||||
<<"top_targets_by_reports">> => lists:map(fun({TargetType, TargetId, Count}) ->
|
||||
#{
|
||||
<<"target_type">> => atom_to_binary(TargetType, utf8),
|
||||
<<"target_id">> => TargetId,
|
||||
<<"report_count">> => Count
|
||||
}
|
||||
end, Top10)
|
||||
},
|
||||
handler_utils:send_json(Req1, 200, Stats);
|
||||
{error, Code, Msg, Req1} ->
|
||||
handler_utils:send_error(Req1, Code, Msg)
|
||||
end.
|
||||
@@ -0,0 +1,116 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Административный обработчик статистики по отзывам.
|
||||
%%% GET /v1/admin/reviews/stats – возвращает общее количество,
|
||||
%%% распределение по типам цели, статусам, топ-10 целей по отзывам
|
||||
%%% (всего, позитивных, негативных).
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_handler_reviews_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_review_stats(Req);
|
||||
_ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>)
|
||||
end.
|
||||
|
||||
%%% Swagger metadata
|
||||
-spec trails() -> [map()].
|
||||
trails() ->
|
||||
[
|
||||
#{
|
||||
path => <<"/v1/admin/reviews/stats">>,
|
||||
method => <<"GET">>,
|
||||
description => <<"Get detailed review statistics (total, by target type, by status, top targets by reviews/positive/negative)">>,
|
||||
tags => [<<"Statistics">>],
|
||||
responses => #{
|
||||
200 => #{
|
||||
description => <<"Review statistics object">>,
|
||||
content => #{<<"application/json">> => #{schema => review_stats_schema()}}
|
||||
},
|
||||
403 => #{description => <<"Admin access required">>}
|
||||
}
|
||||
}
|
||||
].
|
||||
|
||||
review_stats_schema() ->
|
||||
#{
|
||||
type => object,
|
||||
properties => #{
|
||||
<<"total_reviews">> => #{type => integer, description => <<"Total number of reviews">>},
|
||||
<<"reviews_by_target_type">> => #{
|
||||
type => object,
|
||||
description => <<"Number of reviews grouped by target type (event, calendar)">>,
|
||||
additionalProperties => #{type => integer}
|
||||
},
|
||||
<<"reviews_by_status">> => #{
|
||||
type => object,
|
||||
description => <<"Number of reviews grouped by status (visible, hidden, deleted)">>,
|
||||
additionalProperties => #{type => integer}
|
||||
},
|
||||
<<"top_targets_by_reviews">> => #{
|
||||
type => array,
|
||||
description => <<"Top 10 targets by total number of reviews">>,
|
||||
items => target_review_count_schema()
|
||||
},
|
||||
<<"top_targets_by_positive_reviews">> => #{
|
||||
type => array,
|
||||
description => <<"Top 10 targets by positive reviews (rating >= 4)">>,
|
||||
items => target_review_count_schema()
|
||||
},
|
||||
<<"top_targets_by_negative_reviews">> => #{
|
||||
type => array,
|
||||
description => <<"Top 10 targets by negative reviews (rating <= 2)">>,
|
||||
items => target_review_count_schema()
|
||||
}
|
||||
}
|
||||
}.
|
||||
|
||||
target_review_count_schema() ->
|
||||
#{
|
||||
type => object,
|
||||
properties => #{
|
||||
<<"target_type">> => #{type => string},
|
||||
<<"target_id">> => #{type => string},
|
||||
<<"review_count">> => #{type => integer}
|
||||
}
|
||||
}.
|
||||
|
||||
%%% Internal functions
|
||||
|
||||
%% @doc GET /v1/admin/reviews/stats – статистика по отзывам.
|
||||
-spec get_review_stats(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}.
|
||||
get_review_stats(Req) ->
|
||||
case handler_utils:auth_admin(Req) of
|
||||
{ok, _AdminId, Req1} ->
|
||||
Total = core_review:count_reviews(),
|
||||
ByTargetType = core_review:count_reviews_by_target_type(),
|
||||
ByStatus = core_review:count_reviews_by_status(),
|
||||
TopAll = core_review:get_top_targets_by_reviews(10),
|
||||
TopPositive = core_review:get_top_targets_by_positive_reviews(10),
|
||||
TopNegative = core_review:get_top_targets_by_negative_reviews(10),
|
||||
Stats = #{
|
||||
<<"total_reviews">> => Total,
|
||||
<<"reviews_by_target_type">> => maps:from_list([{atom_to_binary(Type, utf8), Count} || {Type, Count} <- ByTargetType]),
|
||||
<<"reviews_by_status">> => maps:from_list([{atom_to_binary(Status, utf8), Count} || {Status, Count} <- ByStatus]),
|
||||
<<"top_targets_by_reviews">> => lists:map(fun target_to_map/1, TopAll),
|
||||
<<"top_targets_by_positive_reviews">> => lists:map(fun target_to_map/1, TopPositive),
|
||||
<<"top_targets_by_negative_reviews">> => lists:map(fun target_to_map/1, TopNegative)
|
||||
},
|
||||
handler_utils:send_json(Req1, 200, Stats);
|
||||
{error, Code, Msg, Req1} ->
|
||||
handler_utils:send_error(Req1, Code, Msg)
|
||||
end.
|
||||
|
||||
target_to_map({TargetType, TargetId, Count}) ->
|
||||
#{
|
||||
<<"target_type">> => atom_to_binary(TargetType, utf8),
|
||||
<<"target_id">> => TargetId,
|
||||
<<"review_count">> => Count
|
||||
}.
|
||||
@@ -47,15 +47,18 @@ 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">>},
|
||||
<<"reviews_total">> => #{type => integer, description => <<"Total number of reviews">>},
|
||||
<<"calendars_total">> => #{type => integer, description => <<"Total number of calendars">>},
|
||||
<<"reports_total">> => #{type => integer, description => <<"Total number of reports">>},
|
||||
<<"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">>},
|
||||
<<"active_subscriptions">> => #{type => integer, description => <<"Number of active subscriptions">>},
|
||||
<<"pending_users_total">> => #{type => integer, description => <<"Number of users awaiting email verification">>},
|
||||
<<"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,
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Административный обработчик статистики по подпискам.
|
||||
%%% GET /v1/admin/subscriptions/stats – возвращает общее количество,
|
||||
%%% распределение по планам, статусам, пробные, заканчивающиеся платные.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_handler_subscription_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_subscription_stats(Req);
|
||||
_ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>)
|
||||
end.
|
||||
|
||||
%%% Swagger metadata
|
||||
-spec trails() -> [map()].
|
||||
trails() ->
|
||||
[
|
||||
#{
|
||||
path => <<"/v1/admin/subscriptions/stats">>,
|
||||
method => <<"GET">>,
|
||||
description => <<"Get detailed subscription statistics (total, by plan, by status, trial count, ending paid subscriptions)">>,
|
||||
tags => [<<"Statistics">>],
|
||||
responses => #{
|
||||
200 => #{
|
||||
description => <<"Subscription statistics object">>,
|
||||
content => #{<<"application/json">> => #{schema => subscription_stats_schema()}}
|
||||
},
|
||||
403 => #{description => <<"Admin access required">>}
|
||||
}
|
||||
}
|
||||
].
|
||||
|
||||
subscription_stats_schema() ->
|
||||
#{
|
||||
type => object,
|
||||
properties => #{
|
||||
<<"total_subscriptions">> => #{type => integer, description => <<"Total number of subscriptions">>},
|
||||
<<"subscriptions_by_plan">> => #{
|
||||
type => object,
|
||||
description => <<"Number of subscriptions grouped by plan">>,
|
||||
additionalProperties => #{type => integer}
|
||||
},
|
||||
<<"subscriptions_by_status">> => #{
|
||||
type => object,
|
||||
description => <<"Number of subscriptions grouped by status">>,
|
||||
additionalProperties => #{type => integer}
|
||||
},
|
||||
<<"trial_subscriptions">> => #{type => integer, description => <<"Number of subscriptions in trial period (trial_used = false)">>},
|
||||
<<"ending_paid_subscriptions">> => #{
|
||||
type => array,
|
||||
description => <<"Paid subscriptions expiring within 30 days">>,
|
||||
items => #{
|
||||
type => object,
|
||||
properties => #{
|
||||
<<"id">> => #{type => string},
|
||||
<<"user_id">> => #{type => string},
|
||||
<<"plan">> => #{type => string},
|
||||
<<"expires_at">> => #{type => string, format => <<"date-time">>}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}.
|
||||
|
||||
%%% Internal functions
|
||||
|
||||
%% @doc GET /v1/admin/subscriptions/stats – статистика по подпискам.
|
||||
-spec get_subscription_stats(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}.
|
||||
get_subscription_stats(Req) ->
|
||||
case handler_utils:auth_admin(Req) of
|
||||
{ok, _AdminId, Req1} ->
|
||||
Total = core_subscription:count_subscriptions(),
|
||||
ByPlan = core_subscription:count_subscriptions_by_plan(),
|
||||
ByStatus = core_subscription:count_subscriptions_by_status(),
|
||||
Trial = core_subscription:count_trial_subscriptions(),
|
||||
EndingPaid = core_subscription:get_ending_paid_subscriptions(30),
|
||||
Stats = #{
|
||||
<<"total_subscriptions">> => Total,
|
||||
<<"subscriptions_by_plan">> => maps:from_list([{atom_to_binary(Plan, utf8), Count} || {Plan, Count} <- ByPlan]),
|
||||
<<"subscriptions_by_status">> => maps:from_list([{atom_to_binary(Status, utf8), Count} || {Status, Count} <- ByStatus]),
|
||||
<<"trial_subscriptions">> => Trial,
|
||||
<<"ending_paid_subscriptions">> => lists:map(fun(Sub) ->
|
||||
#{
|
||||
<<"id">> => Sub#subscription.id,
|
||||
<<"user_id">> => Sub#subscription.user_id,
|
||||
<<"plan">> => atom_to_binary(Sub#subscription.plan, utf8),
|
||||
<<"expires_at">> => handler_utils:datetime_to_iso8601(Sub#subscription.expires_at)
|
||||
}
|
||||
end, EndingPaid)
|
||||
},
|
||||
handler_utils:send_json(Req1, 200, Stats);
|
||||
{error, Code, Msg, Req1} ->
|
||||
handler_utils:send_error(Req1, Code, Msg)
|
||||
end.
|
||||
@@ -1,12 +1,10 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Административный обработчик для получения статистики по тикетам.
|
||||
%%% GET – возвращает агрегированную статистику тикетов
|
||||
%%% (количество по статусам: open, in_progress, resolved, closed).
|
||||
%%% @doc Административный обработчик статистики по тикетам.
|
||||
%%% GET – возвращает агрегированные данные по тикетам.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_handler_ticket_stats).
|
||||
-behaviour(cowboy_handler).
|
||||
|
||||
-export([init/2]).
|
||||
-export([trails/0]).
|
||||
|
||||
@@ -16,8 +14,8 @@
|
||||
-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">>)
|
||||
<<"GET">> -> get_ticket_stats(Req);
|
||||
_ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>)
|
||||
end.
|
||||
|
||||
%%% Swagger metadata
|
||||
@@ -25,38 +23,41 @@ init(Req, _Opts) ->
|
||||
trails() ->
|
||||
[
|
||||
#{
|
||||
path => <<"/v1/admin/tickets/stats">>,
|
||||
method => <<"GET">>,
|
||||
description => <<"Get ticket statistics (admin)">>,
|
||||
tags => [<<"Tickets">>],
|
||||
responses => #{
|
||||
path => <<"/v1/admin/tickets/stats">>,
|
||||
method => <<"GET">>,
|
||||
description => <<"Get ticket statistics">>,
|
||||
tags => [<<"Tickets">>],
|
||||
responses => #{
|
||||
200 => #{
|
||||
description => <<"Ticket statistics">>,
|
||||
content => #{<<"application/json">> => #{schema => #{
|
||||
type => object,
|
||||
properties => #{
|
||||
open => #{type => integer, description => <<"Number of open tickets">>},
|
||||
in_progress => #{type => integer, description => <<"Number of tickets in progress">>},
|
||||
resolved => #{type => integer, description => <<"Number of resolved tickets">>},
|
||||
closed => #{type => integer, description => <<"Number of closed tickets">>},
|
||||
total => #{type => integer, description => <<"Total number of tickets">>}
|
||||
}
|
||||
}}}
|
||||
},
|
||||
403 => #{description => <<"Admin access required">>}
|
||||
content => #{<<"application/json">> => #{schema => ticket_stats_schema()}}
|
||||
}
|
||||
}
|
||||
}
|
||||
].
|
||||
|
||||
ticket_stats_schema() ->
|
||||
#{
|
||||
type => object,
|
||||
properties => #{
|
||||
open => #{type => integer, description => <<"Number of open tickets">>},
|
||||
in_progress => #{type => integer, description => <<"Number of in-progress tickets">>},
|
||||
resolved => #{type => integer, description => <<"Number of resolved tickets">>},
|
||||
closed => #{type => integer, description => <<"Number of closed tickets">>},
|
||||
total_tickets => #{type => integer, description => <<"Total number of tickets">>},
|
||||
total_errors => #{type => integer, description => <<"Total error occurrences">>}
|
||||
}
|
||||
}.
|
||||
|
||||
%%% Internal functions
|
||||
|
||||
%% @doc Получить статистику тикетов. Доступно только администраторам.
|
||||
-spec get_stats(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}.
|
||||
get_stats(Req) ->
|
||||
%% @doc GET /v1/admin/tickets/stats – получить статистику по тикетам.
|
||||
-spec get_ticket_stats(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}.
|
||||
get_ticket_stats(Req) ->
|
||||
case handler_utils:auth_admin(Req) of
|
||||
{ok, _AdminId, Req1} ->
|
||||
Stats = core_ticket:stats(),
|
||||
{ok, AdminId, Req1} ->
|
||||
Stats = logic_ticket:get_statistics(AdminId),
|
||||
handler_utils:send_json(Req1, 200, Stats);
|
||||
{error, Code, Message, Req1} ->
|
||||
handler_utils:send_error(Req1, Code, Message)
|
||||
{error, Code, Msg, Req1} ->
|
||||
handler_utils:send_error(Req1, Code, Msg)
|
||||
end.
|
||||
@@ -0,0 +1,80 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% @doc Административный обработчик статистики по пользователям.
|
||||
%%% GET /v1/admin/users/stats – возвращает общее количество,
|
||||
%%% распределение по ролям, статусам и число неподтверждённых.
|
||||
%%% @end
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_handler_user_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_user_stats(Req);
|
||||
_ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>)
|
||||
end.
|
||||
|
||||
%%% Swagger metadata
|
||||
-spec trails() -> [map()].
|
||||
trails() ->
|
||||
[
|
||||
#{
|
||||
path => <<"/v1/admin/users/stats">>,
|
||||
method => <<"GET">>,
|
||||
description => <<"Get detailed user statistics (total, by role, by status, pending)">>,
|
||||
tags => [<<"Statistics">>],
|
||||
responses => #{
|
||||
200 => #{
|
||||
description => <<"User statistics object">>,
|
||||
content => #{<<"application/json">> => #{schema => user_stats_schema()}}
|
||||
},
|
||||
403 => #{description => <<"Admin access required">>}
|
||||
}
|
||||
}
|
||||
].
|
||||
|
||||
user_stats_schema() ->
|
||||
#{
|
||||
type => object,
|
||||
properties => #{
|
||||
<<"total_users">> => #{type => integer, description => <<"Total number of users">>},
|
||||
<<"users_by_role">> => #{
|
||||
type => object,
|
||||
description => <<"Number of users grouped by role">>,
|
||||
additionalProperties => #{type => integer}
|
||||
},
|
||||
<<"users_by_status">> => #{
|
||||
type => object,
|
||||
description => <<"Number of users grouped by status">>,
|
||||
additionalProperties => #{type => integer}
|
||||
},
|
||||
<<"pending_users">> => #{type => integer, description => <<"Number of unverified users">>}
|
||||
}
|
||||
}.
|
||||
|
||||
%%% Internal functions
|
||||
|
||||
%% @doc GET /v1/admin/users/stats – статистика по пользователям.
|
||||
-spec get_user_stats(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}.
|
||||
get_user_stats(Req) ->
|
||||
case handler_utils:auth_admin(Req) of
|
||||
{ok, _AdminId, Req1} ->
|
||||
Total = core_user:count_users(),
|
||||
ByRole = core_user:count_users_by_role(),
|
||||
ByStatus = core_user:count_users_by_status(),
|
||||
Pending = core_user:count_pending_users(),
|
||||
Stats = #{
|
||||
<<"total_users">> => Total,
|
||||
<<"users_by_role">> => maps:from_list([{atom_to_binary(Role, utf8), Count} || {Role, Count} <- ByRole]),
|
||||
<<"users_by_status">> => maps:from_list([{atom_to_binary(Status, utf8), Count} || {Status, Count} <- ByStatus]),
|
||||
<<"pending_users">> => Pending
|
||||
},
|
||||
handler_utils:send_json(Req1, 200, Stats);
|
||||
{error, Code, Msg, Req1} ->
|
||||
handler_utils:send_error(Req1, Code, Msg)
|
||||
end.
|
||||
Reference in New Issue
Block a user