Files
EventHubBack/src/handlers/admin/admin_handler_subscriptions.erl
T

119 lines
4.4 KiB
Erlang

%%%-------------------------------------------------------------------
%%% @doc Административный обработчик подписок.
%%% GET – список с пагинацией и фильтрацией.
%%% @end
%%%-------------------------------------------------------------------
-module(admin_handler_subscriptions).
-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">> -> list_subscriptions(Req);
_ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>)
end.
-spec trails() -> [map()].
trails() ->
[
#{
path => <<"/v1/admin/subscriptions">>,
method => <<"GET">>,
description => <<"List all subscriptions (admin)">>,
tags => [<<"Subscriptions">>],
parameters => [
#{name => <<"plan">>, in => <<"query">>, schema => #{type => string, enum => [<<"monthly">>, <<"quarterly">>, <<"biannual">>, <<"annual">>]}, description => <<"Filter by plan">>},
#{name => <<"status">>, in => <<"query">>, schema => #{type => string, enum => [<<"active">>, <<"expired">>, <<"cancelled">>]}, description => <<"Filter by status">>},
#{name => <<"limit">>, in => <<"query">>, schema => #{type => integer}, description => <<"Page size">>},
#{name => <<"offset">>, in => <<"query">>, schema => #{type => integer}, description => <<"Offset">>}
],
responses => #{
200 => #{
description => <<"Array of subscriptions">>,
content => #{<<"application/json">> => #{schema => #{
type => array,
items => subscription_schema()
}}}
}
}
}
].
subscription_schema() ->
#{
type => object,
properties => #{
id => #{type => string},
user_id => #{type => string},
plan => #{type => string, enum => [<<"monthly">>, <<"quarterly">>, <<"biannual">>, <<"annual">>]},
status => #{type => string, enum => [<<"active">>, <<"expired">>, <<"cancelled">>]},
trial_used => #{type => boolean},
started_at => #{type => string, format => <<"date-time">>},
expires_at => #{type => string, format => <<"date-time">>},
created_at => #{type => string, format => <<"date-time">>},
updated_at => #{type => string, format => <<"date-time">>}
}
}.
%%% Internal functions
list_subscriptions(Req) ->
case handler_utils:auth_admin(Req) of
{ok, _AdminId, Req1} ->
Filters = parse_subscription_filters(Req1),
Pagination = handler_utils:parse_pagination_params(Req1),
{ok, AllSubscriptions} = core_subscription:list_all(),
Filtered = apply_filters(AllSubscriptions, Filters),
Sorted = sort_subscriptions(Filtered, Pagination),
Total = length(Sorted),
Page = lists:sublist(Sorted, maps:get(offset, Pagination) + 1, maps:get(limit, Pagination)),
Json = [handler_utils:subscription_to_json(S) || S <- Page],
ExtraHeaders = handler_utils:pagination_headers(Pagination, Total),
handler_utils:send_json(Req1, 200, Json, ExtraHeaders);
{error, Code, Msg, Req1} ->
handler_utils:send_error(Req1, Code, Msg)
end.
parse_subscription_filters(Req) ->
Qs = cowboy_req:parse_qs(Req),
#{
plan => proplists:get_value(<<"plan">>, Qs),
status => proplists:get_value(<<"status">>, Qs)
}.
apply_filters(Subs, Filters) ->
PlanBin = maps:get(plan, Filters, undefined),
StatusBin = maps:get(status, Filters, undefined),
F1 = case PlanBin of
undefined -> Subs;
_ ->
Plan = try binary_to_existing_atom(PlanBin, utf8)
catch error:badarg -> PlanBin end,
[S || S <- Subs, S#subscription.plan =:= Plan]
end,
case StatusBin of
undefined -> F1;
_ ->
Status = try binary_to_existing_atom(StatusBin, utf8)
catch error:badarg -> StatusBin end,
[S || S <- F1, S#subscription.status =:= Status]
end.
sort_subscriptions(Subs, #{sort := Sort, order := Order}) ->
Field = binary_to_existing_atom(Sort, utf8),
lists:sort(
fun(A, B) ->
ValA = sub_field(A, Field),
ValB = sub_field(B, Field),
if Order == <<"asc">> -> ValA =< ValB;
true -> ValA >= ValB
end
end, Subs).
sub_field(#subscription{created_at = V}, created_at) -> V;
sub_field(#subscription{expires_at = V}, expires_at) -> V;
sub_field(_, _) -> undefined.