Расширена статистика для разделов #22

This commit is contained in:
2026-05-28 13:49:08 +03:00
parent 2dac5e5102
commit 1986fd4694
20 changed files with 1189 additions and 71 deletions
@@ -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.