-module(admin_handler_calendars). -behaviour(cowboy_handler). -export([init/2]). -export([trails/0]). -export([calendar_schema/0]). -include("records.hrl"). init(Req, _Opts) -> case cowboy_req:method(Req) of <<"GET">> -> list_calendars(Req); _ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>) end. trails() -> [ #{ path => <<"/v1/admin/calendars">>, method => <<"GET">>, description => <<"List all calendars (admin)">>, tags => [<<"Admin Calendars">>], parameters => [ #{name => <<"status">>, in => <<"query">>, schema => #{type => string}, description => <<"Filter by status">>}, #{name => <<"type">>, in => <<"query">>, schema => #{type => string}, description => <<"Filter by type">>}, #{name => <<"q">>, in => <<"query">>, schema => #{type => string}, description => <<"Search in title/description">>}, #{name => <<"sort_by">>, in => <<"query">>, schema => #{type => string}, description => <<"Sort field (title, created_at, updated_at)">>}, #{name => <<"sort_order">>, in => <<"query">>, schema => #{type => string, enum => [<<"asc">>, <<"desc">>]}, description => <<"Sort direction">>}, #{name => <<"limit">>, in => <<"query">>, schema => #{type => integer}, description => <<"Page size">>}, #{name => <<"offset">>, in => <<"query">>, schema => #{type => integer}, description => <<"Offset">>} ], responses => #{ 200 => #{ description => <<"Array of calendars">>, content => #{<<"application/json">> => #{ schema => #{ type => array, items => calendar_schema() } }} }, 403 => #{description => <<"Forbidden">>} } } ]. calendar_schema() -> #{ type => object, properties => #{ id => #{type => string}, owner_id => #{type => string}, title => #{type => string}, description => #{type => string}, short_name => #{type => string}, category => #{type => string}, color => #{type => string}, image_url => #{type => string}, settings => #{type => object}, tags => #{type => array, items => #{type => string}}, type => #{type => string, enum => [<<"personal">>, <<"commercial">>]}, confirmation => #{type => string}, rating_avg => #{type => number}, rating_count => #{type => integer}, status => #{type => string, enum => [<<"active">>, <<"frozen">>, <<"deleted">>]}, reason => #{type => string}, created_at => #{type => string, format => date_time}, updated_at => #{type => string, format => date_time} } }. %%% HTTP-метод list_calendars(Req) -> case handler_utils:auth_admin(Req) of {ok, _AdminId, Req1} -> Pagination = handler_utils:parse_pagination_params(Req1), Filters = parse_calendar_filters(Req1), {ok, AllCalendars} = logic_calendar:admin_list_all(), Filtered = apply_calendar_filters(AllCalendars, Filters), % Формируем параметры сортировки из query-параметров Qs = cowboy_req:parse_qs(Req1), SortBy = proplists:get_value(<<"sort_by">>, Qs, <<"created_at">>), SortOrder = proplists:get_value(<<"sort_order">>, Qs, <<"desc">>), Sorted = sort_calendars(Filtered, SortBy, SortOrder), Total = length(Sorted), Page = lists:sublist(Sorted, maps:get(offset, Pagination) + 1, maps:get(limit, Pagination)), Json = [handler_utils:calendar_to_json(C) || C <- 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_calendar_filters(Req) -> Qs = cowboy_req:parse_qs(Req), #{ status => proplists:get_value(<<"status">>, Qs), type => proplists:get_value(<<"type">>, Qs), q => proplists:get_value(<<"q">>, Qs) }. apply_calendar_filters(Calendars, Filters) -> StatusBin = maps:get(status, Filters), TypeBin = maps:get(type, Filters), Q = maps:get(q, Filters), F1 = case StatusBin of undefined -> Calendars; _ -> Status = binary_to_existing_atom(StatusBin, utf8), [C || C <- Calendars, C#calendar.status =:= Status] end, F2 = case TypeBin of undefined -> F1; _ -> Type = binary_to_existing_atom(TypeBin, utf8), [C || C <- F1, C#calendar.type =:= Type] end, case Q of undefined -> F2; _ -> QLower = string:lowercase(binary_to_list(Q)), [C || C <- F2, string:str(string:lowercase(binary_to_list(C#calendar.title)), QLower) > 0 orelse string:str(string:lowercase(binary_to_list(C#calendar.description)), QLower) > 0] end. %%% Сортировка sort_calendars(Calendars, SortBy, SortOrder) -> Index = case SortBy of <<"title">> -> #calendar.title; % элемент 3 <<"created_at">> -> #calendar.created_at; % элемент 17 <<"updated_at">> -> #calendar.updated_at; % элемент 18 _ -> #calendar.created_at end, Sorted = lists:sort(fun(A, B) -> VA = element(Index, A), VB = element(Index, B), case SortOrder of <<"desc">> -> VA > VB; _ -> VA < VB end end, Calendars), Sorted.