-module(admin_handler_events). -behaviour(cowboy_handler). -export([init/2]). -export([trails/0]). -include("records.hrl"). %%%=================================================================== %%% cowboy_handler callback %%%=================================================================== init(Req, _Opts) -> case cowboy_req:method(Req) of <<"GET">> -> list_all_events(Req); _ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>) end. %%%=================================================================== %%% Swagger metadata %%%=================================================================== trails() -> [ #{ path => <<"/v1/admin/events">>, method => <<"GET">>, description => <<"Search and list events (admin)">>, tags => [<<"Events">>], parameters => [ #{name => <<"from">>, in => <<"query">>, description => <<"ISO8601 start datetime">>, required => false, schema => #{type => string}}, #{name => <<"to">>, in => <<"query">>, description => <<"ISO8601 end datetime">>, required => false, schema => #{type => string}}, #{name => <<"status">>, in => <<"query">>, description => <<"active, cancelled, completed, or all">>, required => false, schema => #{type => string}}, #{name => <<"calendar_id">>, in => <<"query">>, description => <<"Filter by calendar ID">>, required => false, schema => #{type => string}}, #{name => <<"title">>, in => <<"query">>, description => <<"Exact title match">>, required => false, schema => #{type => string}}, #{name => <<"q">>, in => <<"query">>, description => <<"Substring search in title/description">>, required => false, schema => #{type => string}}, #{name => <<"limit">>, in => <<"query">>, description => <<"Page size (max 200)">>, required => false, schema => #{type => integer}}, #{name => <<"offset">>, in => <<"query">>, description => <<"Offset">>, required => false, schema => #{type => integer}}, #{name => <<"sort">>, in => <<"query">>, description => <<"created_at, start_time, title, status">>, required => false, schema => #{type => string}}, #{name => <<"order">>, in => <<"query">>, description => <<"asc or desc">>, required => false, schema => #{type => string, enum => [<<"asc">>, <<"desc">>]}} ], responses => #{ 200 => #{ description => <<"Array of events with Content-Range header">>, content => #{<<"application/json">> => #{schema => #{ type => array, items => event_schema() }}} }, 405 => #{description => <<"Method not allowed">>} } } ]. event_schema() -> #{ type => object, properties => #{ id => #{type => string}, calendar_id => #{type => string}, title => #{type => string}, description => #{type => string}, event_type => #{type => string, enum => [<<"single">>, <<"recurring">>]}, start_time => #{type => string, format => <<"date-time">>}, duration => #{type => integer}, recurrence => #{type => object, nullable => true}, master_id => #{type => string, nullable => true}, is_instance => #{type => boolean}, specialist_id => #{type => string, nullable => true}, location => #{type => object, nullable => true}, tags => #{type => array, items => #{type => string}}, capacity => #{type => integer, nullable => true}, online_link => #{type => string, nullable => true}, status => #{type => string, enum => [<<"active">>, <<"cancelled">>, <<"completed">>]}, reason => #{type => string, nullable => true}, rating_avg => #{type => number, format => float}, rating_count => #{type => integer}, attachments => #{type => array, items => #{type => string}, nullable => true}, edit_history => #{type => array, items => #{type => object}, nullable => true}, created_at => #{type => string, format => <<"date-time">>}, updated_at => #{type => string, format => <<"date-time">>} } }. %%%=================================================================== %%% Internal functions %%%=================================================================== list_all_events(Req) -> case handler_utils:auth_admin(Req) of {ok, _AdminId, Req1} -> Params = parse_admin_event_search(Req1), {ok, Total, Events} = logic_event:search_events(Params), Json = [handler_utils:event_to_json(E) || E <- Events], Limit = maps:get(limit, Params, 50), Offset = maps:get(offset, Params, 0), RangeEnd = min(Offset + Limit - 1, Total - 1), Headers = #{ <<"content-type">> => <<"application/json">>, <<"content-range">> => iolist_to_binary(io_lib:format("items ~B-~B/~B", [Offset, RangeEnd, Total])), <<"x-total-count">> => integer_to_binary(Total), <<"access-control-expose-headers">> => <<"Content-Range, X-Total-Count">> }, handler_utils:send_json(Req1, 200, Json, Headers); {error, Code, Msg, Req1} -> handler_utils:send_error(Req1, Code, Msg) end. parse_admin_event_search(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)), status => proplists:get_value(<<"status">>, Qs, undefined), calendar_id => proplists:get_value(<<"calendar_id">>, Qs, undefined), title => proplists:get_value(<<"title">>, Qs, undefined), q => proplists:get_value(<<"q">>, Qs, undefined), limit => handler_utils:parse_int_qs(proplists:get_value(<<"limit">>, Qs), 50), offset => handler_utils:parse_int_qs(proplists:get_value(<<"offset">>, Qs), 0), sort => proplists:get_value(<<"sort">>, Qs, <<"created_at">>), order => proplists:get_value(<<"order">>, Qs, <<"desc">>) }.