Добавлены эндпойнты admin/v1/events и admin/v1/events/:id #20
This commit is contained in:
@@ -6,6 +6,7 @@
|
|||||||
-export([generate_id/0]).
|
-export([generate_id/0]).
|
||||||
-export([count_events/0, count_events_by_date/2]).
|
-export([count_events/0, count_events_by_date/2]).
|
||||||
-export([freeze/2, unfreeze/2]).
|
-export([freeze/2, unfreeze/2]).
|
||||||
|
-export([list_all/0]).
|
||||||
|
|
||||||
%% Создание одиночного события
|
%% Создание одиночного события
|
||||||
create(CalendarId, Title, StartTime, Duration) ->
|
create(CalendarId, Title, StartTime, Duration) ->
|
||||||
@@ -172,6 +173,10 @@ delete(Id) ->
|
|||||||
count_events() ->
|
count_events() ->
|
||||||
mnesia:table_info(event, size).
|
mnesia:table_info(event, size).
|
||||||
|
|
||||||
|
list_all() ->
|
||||||
|
Match = #event{status = active, is_instance = false, _ = '_'},
|
||||||
|
mnesia:dirty_match_object(Match).
|
||||||
|
|
||||||
count_events_by_date(From, To) ->
|
count_events_by_date(From, To) ->
|
||||||
All = mnesia:dirty_match_object(#event{_ = '_'}),
|
All = mnesia:dirty_match_object(#event{_ = '_'}),
|
||||||
Filtered = lists:filter(fun(E) ->
|
Filtered = lists:filter(fun(E) ->
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ stats() ->
|
|||||||
|
|
||||||
%% ── новые функции ──────────────────────────────────────
|
%% ── новые функции ──────────────────────────────────────
|
||||||
create_ticket(Data) ->
|
create_ticket(Data) ->
|
||||||
Id = base64:encode(crypto:strong_rand_bytes(9)),
|
Id = base64:encode(crypto:strong_rand_bytes(9), #{mode => urlsafe, padding => false}),
|
||||||
Now = calendar:universal_time(),
|
Now = calendar:universal_time(),
|
||||||
Ticket = #ticket{
|
Ticket = #ticket{
|
||||||
id = Id,
|
id = Id,
|
||||||
|
|||||||
@@ -107,6 +107,9 @@ start_admin_http() ->
|
|||||||
% ================== ПОЛЬЗОВАТЕЛИ ==================
|
% ================== ПОЛЬЗОВАТЕЛИ ==================
|
||||||
{"/v1/admin/users", admin_handler_users, []},
|
{"/v1/admin/users", admin_handler_users, []},
|
||||||
{"/v1/admin/users/:id", admin_handler_user_by_id, []},
|
{"/v1/admin/users/:id", admin_handler_user_by_id, []},
|
||||||
|
% ================== СОБЫТИЯ ==================
|
||||||
|
{"/v1/admin/events", admin_handler_events, []},
|
||||||
|
{"/v1/admin/events/:id", admin_handler_event_by_id, []},
|
||||||
% ================== ОТЧЁТЫ ==================
|
% ================== ОТЧЁТЫ ==================
|
||||||
{"/v1/admin/reports", admin_handler_reports, []},
|
{"/v1/admin/reports", admin_handler_reports, []},
|
||||||
{"/v1/admin/reports/:id", admin_handler_report_by_id, []},
|
{"/v1/admin/reports/:id", admin_handler_report_by_id, []},
|
||||||
|
|||||||
195
src/handlers/admin/admin_handler_event_by_id.erl
Normal file
195
src/handlers/admin/admin_handler_event_by_id.erl
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
-module(admin_handler_event_by_id).
|
||||||
|
-behaviour(cowboy_handler).
|
||||||
|
|
||||||
|
-export([init/2]).
|
||||||
|
-include("records.hrl").
|
||||||
|
|
||||||
|
init(Req, _Opts) ->
|
||||||
|
case cowboy_req:method(Req) of
|
||||||
|
<<"GET">> -> get_event(Req);
|
||||||
|
<<"PUT">> -> update_event(Req);
|
||||||
|
<<"DELETE">> -> delete_event(Req);
|
||||||
|
_ -> send_error(Req, 405, <<"Method not allowed">>)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% GET /v1/admin/events/:id
|
||||||
|
get_event(Req) ->
|
||||||
|
case auth_admin(Req) of
|
||||||
|
{ok, _AdminId, Req1} ->
|
||||||
|
EventId = cowboy_req:binding(id, Req1),
|
||||||
|
case logic_event:get_event_admin(EventId) of
|
||||||
|
{ok, Event} ->
|
||||||
|
send_json(Req1, 200, event_to_json(Event));
|
||||||
|
{error, not_found} ->
|
||||||
|
send_error(Req1, 404, <<"Event not found">>);
|
||||||
|
{error, _} ->
|
||||||
|
send_error(Req1, 500, <<"Internal server error">>)
|
||||||
|
end;
|
||||||
|
{error, Code, Msg, Req1} ->
|
||||||
|
send_error(Req1, Code, Msg)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% PUT /v1/admin/events/:id
|
||||||
|
update_event(Req) ->
|
||||||
|
case auth_admin(Req) of
|
||||||
|
{ok, _AdminId, Req1} ->
|
||||||
|
EventId = cowboy_req:binding(id, Req1),
|
||||||
|
{ok, Body, Req2} = cowboy_req:read_body(Req1),
|
||||||
|
try jsx:decode(Body, [return_maps]) of
|
||||||
|
UpdatesMap when is_map(UpdatesMap) ->
|
||||||
|
Updates = maps:to_list(UpdatesMap),
|
||||||
|
UpdatesWithTypes = convert_fields(Updates),
|
||||||
|
case logic_event:update_event_admin(EventId, UpdatesWithTypes) of
|
||||||
|
{ok, Event} ->
|
||||||
|
send_json(Req2, 200, event_to_json(Event));
|
||||||
|
{error, not_found} ->
|
||||||
|
send_error(Req2, 404, <<"Event not found">>);
|
||||||
|
{error, _} ->
|
||||||
|
send_error(Req2, 500, <<"Internal server error">>)
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
send_error(Req2, 400, <<"Invalid JSON">>)
|
||||||
|
catch
|
||||||
|
_:_ -> send_error(Req1, 400, <<"Invalid JSON format">>)
|
||||||
|
end;
|
||||||
|
{error, Code, Msg, Req1} ->
|
||||||
|
send_error(Req1, Code, Msg)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% DELETE /v1/admin/events/:id
|
||||||
|
delete_event(Req) ->
|
||||||
|
case auth_admin(Req) of
|
||||||
|
{ok, _AdminId, Req1} ->
|
||||||
|
EventId = cowboy_req:binding(id, Req1),
|
||||||
|
case logic_event:delete_event_admin(EventId) of
|
||||||
|
{ok, _} ->
|
||||||
|
send_json(Req1, 200, #{status => <<"deleted">>});
|
||||||
|
{error, not_found} ->
|
||||||
|
send_error(Req1, 404, <<"Event not found">>);
|
||||||
|
{error, _} ->
|
||||||
|
send_error(Req1, 500, <<"Internal server error">>)
|
||||||
|
end;
|
||||||
|
{error, Code, Msg, Req1} ->
|
||||||
|
send_error(Req1, Code, Msg)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% --- Вспомогательные функции (идентичны handler_event_by_id.erl) ---
|
||||||
|
|
||||||
|
auth_admin(Req) ->
|
||||||
|
case handler_auth:authenticate(Req) of
|
||||||
|
{ok, AdminId, Req1} ->
|
||||||
|
case admin_utils:is_admin(AdminId) of
|
||||||
|
true -> {ok, AdminId, Req1};
|
||||||
|
false -> {error, 403, <<"Admin access required">>, Req1}
|
||||||
|
end;
|
||||||
|
{error, Code, Msg, Req1} ->
|
||||||
|
{error, Code, Msg, Req1}
|
||||||
|
end.
|
||||||
|
|
||||||
|
convert_fields(Updates) ->
|
||||||
|
lists:map(fun convert_field/1, Updates).
|
||||||
|
|
||||||
|
convert_field({<<"title">>, Val}) -> {title, Val};
|
||||||
|
convert_field({<<"description">>, Val}) -> {description, Val};
|
||||||
|
convert_field({<<"event_type">>, Val}) -> {event_type, Val};
|
||||||
|
convert_field({<<"start_time">>, Val}) ->
|
||||||
|
case parse_datetime(Val) of
|
||||||
|
{ok, Dt} -> {start_time, Dt};
|
||||||
|
_ -> {start_time, Val}
|
||||||
|
end;
|
||||||
|
convert_field({<<"duration">>, Val}) -> {duration, Val};
|
||||||
|
convert_field({<<"recurrence">>, Val}) ->
|
||||||
|
RuleJson = jsx:encode(Val),
|
||||||
|
{recurrence_rule, RuleJson};
|
||||||
|
convert_field({<<"specialist_id">>, Val}) -> {specialist_id, Val};
|
||||||
|
convert_field({<<"location">>, Val}) when is_map(Val) ->
|
||||||
|
Loc = #location{
|
||||||
|
address = maps:get(<<"address">>, Val, undefined),
|
||||||
|
lat = maps:get(<<"lat">>, Val, undefined),
|
||||||
|
lon = maps:get(<<"lon">>, Val, undefined)
|
||||||
|
},
|
||||||
|
{location, Loc};
|
||||||
|
convert_field({<<"location">>, Val}) -> {location, Val};
|
||||||
|
convert_field({<<"tags">>, Val}) -> {tags, Val};
|
||||||
|
convert_field({<<"capacity">>, Val}) -> {capacity, Val};
|
||||||
|
convert_field({<<"online_link">>, Val}) -> {online_link, Val};
|
||||||
|
convert_field({<<"status">>, Val}) -> {status, Val};
|
||||||
|
convert_field(Other) -> Other.
|
||||||
|
|
||||||
|
%% event_to_json, datetime_to_iso8601, parse_datetime, parse_datetime_binary
|
||||||
|
%% берутся те же, что и в admin_handler_events.erl (можно вынести в общий модуль,
|
||||||
|
%% но для простоты дублируем).
|
||||||
|
event_to_json(Event) ->
|
||||||
|
LocationJson = case Event#event.location of
|
||||||
|
undefined -> null;
|
||||||
|
#location{address = Addr, lat = Lat, lon = Lon} ->
|
||||||
|
#{address => Addr, lat => Lat, lon => Lon}
|
||||||
|
end,
|
||||||
|
RecurrenceJson = case Event#event.recurrence_rule of
|
||||||
|
undefined -> null;
|
||||||
|
Rule ->
|
||||||
|
try jsx:decode(Rule, [return_maps]) of
|
||||||
|
Map when is_map(Map) -> Map;
|
||||||
|
_ -> null
|
||||||
|
catch _:_ -> null
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
#{
|
||||||
|
id => Event#event.id,
|
||||||
|
calendar_id => Event#event.calendar_id,
|
||||||
|
title => Event#event.title,
|
||||||
|
description => Event#event.description,
|
||||||
|
event_type => Event#event.event_type,
|
||||||
|
start_time => datetime_to_iso8601(Event#event.start_time),
|
||||||
|
duration => Event#event.duration,
|
||||||
|
recurrence => RecurrenceJson,
|
||||||
|
master_id => Event#event.master_id,
|
||||||
|
is_instance => Event#event.is_instance,
|
||||||
|
specialist_id => Event#event.specialist_id,
|
||||||
|
location => LocationJson,
|
||||||
|
tags => Event#event.tags,
|
||||||
|
capacity => Event#event.capacity,
|
||||||
|
online_link => Event#event.online_link,
|
||||||
|
status => Event#event.status,
|
||||||
|
rating_avg => Event#event.rating_avg,
|
||||||
|
rating_count => Event#event.rating_count,
|
||||||
|
created_at => datetime_to_iso8601(Event#event.created_at),
|
||||||
|
updated_at => datetime_to_iso8601(Event#event.updated_at)
|
||||||
|
}.
|
||||||
|
|
||||||
|
datetime_to_iso8601({{Year, Month, Day}, {Hour, Minute, Second}}) ->
|
||||||
|
iolist_to_binary(
|
||||||
|
io_lib:format(
|
||||||
|
"~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ",
|
||||||
|
[Year, Month, Day, Hour, Minute, Second]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
datetime_to_iso8601(undefined) ->
|
||||||
|
undefined.
|
||||||
|
|
||||||
|
parse_datetime(Str) ->
|
||||||
|
try
|
||||||
|
[DateStr, TimeStr] = string:split(Str, "T"),
|
||||||
|
TimeStrNoZ = string:trim(TimeStr, trailing, "Z"),
|
||||||
|
[YearStr, MonthStr, DayStr] = string:split(DateStr, "-", all),
|
||||||
|
[HourStr, MinuteStr, SecondStr] = string:split(TimeStrNoZ, ":", all),
|
||||||
|
Year = binary_to_integer(list_to_binary(YearStr)),
|
||||||
|
Month = binary_to_integer(list_to_binary(MonthStr)),
|
||||||
|
Day = binary_to_integer(list_to_binary(DayStr)),
|
||||||
|
Hour = binary_to_integer(list_to_binary(HourStr)),
|
||||||
|
Minute = binary_to_integer(list_to_binary(MinuteStr)),
|
||||||
|
Second = binary_to_integer(list_to_binary(SecondStr)),
|
||||||
|
{ok, {{Year, Month, Day}, {Hour, Minute, Second}}}
|
||||||
|
catch _:_ -> {error, invalid_format}
|
||||||
|
end.
|
||||||
|
|
||||||
|
send_json(Req, Status, Data) ->
|
||||||
|
Body = jsx:encode(Data),
|
||||||
|
Headers = #{<<"content-type">> => <<"application/json">>},
|
||||||
|
cowboy_req:reply(Status, Headers, Body, Req),
|
||||||
|
{ok, Body, []}.
|
||||||
|
|
||||||
|
send_error(Req, Status, Message) ->
|
||||||
|
Body = jsx:encode(#{error => Message}),
|
||||||
|
cowboy_req:reply(Status, #{<<"content-type">> => <<"application/json">>}, Body, Req),
|
||||||
|
{ok, Body, []}.
|
||||||
131
src/handlers/admin/admin_handler_events.erl
Normal file
131
src/handlers/admin/admin_handler_events.erl
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
-module(admin_handler_events).
|
||||||
|
-behaviour(cowboy_handler).
|
||||||
|
|
||||||
|
-export([init/2]).
|
||||||
|
-include("records.hrl").
|
||||||
|
|
||||||
|
init(Req, _Opts) ->
|
||||||
|
case cowboy_req:method(Req) of
|
||||||
|
<<"GET">> ->
|
||||||
|
list_all_events(Req);
|
||||||
|
_ ->
|
||||||
|
send_error(Req, 405, <<"Method not allowed">>)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% GET /v1/admin/events
|
||||||
|
list_all_events(Req) ->
|
||||||
|
case auth_admin(Req) of
|
||||||
|
{ok, _AdminId, Req1} ->
|
||||||
|
Filters = parse_filters(Req1),
|
||||||
|
{ok, Events} = logic_event:list_all_events(Filters),
|
||||||
|
Json = [event_to_json(E) || E <- Events],
|
||||||
|
send_json(Req1, 200, Json);
|
||||||
|
{error, Code, Msg, Req1} ->
|
||||||
|
send_error(Req1, Code, Msg)
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% --- Вспомогательные функции ---
|
||||||
|
|
||||||
|
parse_filters(Req) ->
|
||||||
|
Qs = cowboy_req:parse_qs(Req),
|
||||||
|
lists:filtermap(
|
||||||
|
fun
|
||||||
|
({<<"from">>, Val}) -> {true, {from, parse_datetime_binary(Val)}};
|
||||||
|
({<<"to">>, Val}) -> {true, {to, parse_datetime_binary(Val)}};
|
||||||
|
(_) -> false
|
||||||
|
end,
|
||||||
|
Qs
|
||||||
|
).
|
||||||
|
|
||||||
|
auth_admin(Req) ->
|
||||||
|
case handler_auth:authenticate(Req) of
|
||||||
|
{ok, AdminId, Req1} ->
|
||||||
|
case admin_utils:is_admin(AdminId) of
|
||||||
|
true -> {ok, AdminId, Req1};
|
||||||
|
false -> {error, 403, <<"Admin access required">>, Req1}
|
||||||
|
end;
|
||||||
|
{error, Code, Msg, Req1} ->
|
||||||
|
{error, Code, Msg, Req1}
|
||||||
|
end.
|
||||||
|
|
||||||
|
%% Сериализация события (полностью скопирована из handler_event_by_id.erl)
|
||||||
|
event_to_json(Event) ->
|
||||||
|
LocationJson = case Event#event.location of
|
||||||
|
undefined -> null;
|
||||||
|
#location{address = Addr, lat = Lat, lon = Lon} ->
|
||||||
|
#{address => Addr, lat => Lat, lon => Lon}
|
||||||
|
end,
|
||||||
|
RecurrenceJson = case Event#event.recurrence_rule of
|
||||||
|
undefined -> null;
|
||||||
|
Rule ->
|
||||||
|
try jsx:decode(Rule, [return_maps]) of
|
||||||
|
Map when is_map(Map) -> Map;
|
||||||
|
_ -> null
|
||||||
|
catch _:_ -> null
|
||||||
|
end
|
||||||
|
end,
|
||||||
|
#{
|
||||||
|
id => Event#event.id,
|
||||||
|
calendar_id => Event#event.calendar_id,
|
||||||
|
title => Event#event.title,
|
||||||
|
description => Event#event.description,
|
||||||
|
event_type => Event#event.event_type,
|
||||||
|
start_time => datetime_to_iso8601(Event#event.start_time),
|
||||||
|
duration => Event#event.duration,
|
||||||
|
recurrence => RecurrenceJson,
|
||||||
|
master_id => Event#event.master_id,
|
||||||
|
is_instance => Event#event.is_instance,
|
||||||
|
specialist_id => Event#event.specialist_id,
|
||||||
|
location => LocationJson,
|
||||||
|
tags => Event#event.tags,
|
||||||
|
capacity => Event#event.capacity,
|
||||||
|
online_link => Event#event.online_link,
|
||||||
|
status => Event#event.status,
|
||||||
|
rating_avg => Event#event.rating_avg,
|
||||||
|
rating_count => Event#event.rating_count,
|
||||||
|
created_at => datetime_to_iso8601(Event#event.created_at),
|
||||||
|
updated_at => datetime_to_iso8601(Event#event.updated_at)
|
||||||
|
}.
|
||||||
|
|
||||||
|
datetime_to_iso8601({{Year, Month, Day}, {Hour, Minute, Second}}) ->
|
||||||
|
iolist_to_binary(
|
||||||
|
io_lib:format(
|
||||||
|
"~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ",
|
||||||
|
[Year, Month, Day, Hour, Minute, Second]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
datetime_to_iso8601(undefined) ->
|
||||||
|
undefined.
|
||||||
|
|
||||||
|
parse_datetime_binary(Str) ->
|
||||||
|
case parse_datetime(Str) of
|
||||||
|
{ok, Dt} -> Dt;
|
||||||
|
_ -> undefined
|
||||||
|
end.
|
||||||
|
|
||||||
|
parse_datetime(Str) ->
|
||||||
|
try
|
||||||
|
[DateStr, TimeStr] = string:split(Str, "T"),
|
||||||
|
TimeStrNoZ = string:trim(TimeStr, trailing, "Z"),
|
||||||
|
[YearStr, MonthStr, DayStr] = string:split(DateStr, "-", all),
|
||||||
|
[HourStr, MinuteStr, SecondStr] = string:split(TimeStrNoZ, ":", all),
|
||||||
|
Year = binary_to_integer(list_to_binary(YearStr)),
|
||||||
|
Month = binary_to_integer(list_to_binary(MonthStr)),
|
||||||
|
Day = binary_to_integer(list_to_binary(DayStr)),
|
||||||
|
Hour = binary_to_integer(list_to_binary(HourStr)),
|
||||||
|
Minute = binary_to_integer(list_to_binary(MinuteStr)),
|
||||||
|
Second = binary_to_integer(list_to_binary(SecondStr)),
|
||||||
|
{ok, {{Year, Month, Day}, {Hour, Minute, Second}}}
|
||||||
|
catch _:_ -> {error, invalid_format}
|
||||||
|
end.
|
||||||
|
|
||||||
|
send_json(Req, Status, Data) ->
|
||||||
|
Body = jsx:encode(Data),
|
||||||
|
Headers = #{<<"content-type">> => <<"application/json">>},
|
||||||
|
cowboy_req:reply(Status, Headers, Body, Req),
|
||||||
|
{ok, Body, []}.
|
||||||
|
|
||||||
|
send_error(Req, Status, Message) ->
|
||||||
|
Body = jsx:encode(#{error => Message}),
|
||||||
|
cowboy_req:reply(Status, #{<<"content-type">> => <<"application/json">>}, Body, Req),
|
||||||
|
{ok, Body, []}.
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
update_event/3, delete_event/2]).
|
update_event/3, delete_event/2]).
|
||||||
-export([validate_event_time/1, validate_event_time/2, get_occurrences/3, cancel_occurrence/3]).
|
-export([validate_event_time/1, validate_event_time/2, get_occurrences/3, cancel_occurrence/3]).
|
||||||
-export([materialize_for_booking/3]).
|
-export([materialize_for_booking/3]).
|
||||||
|
-export([list_all_events/1, get_event_admin/1, update_event_admin/2, delete_event_admin/1]).
|
||||||
|
|
||||||
%% Создание одиночного события
|
%% Создание одиночного события
|
||||||
create_event(UserId, CalendarId, Title, StartTime, Duration) ->
|
create_event(UserId, CalendarId, Title, StartTime, Duration) ->
|
||||||
@@ -235,3 +236,41 @@ merge_materialized(MasterId, Occurrences) ->
|
|||||||
Event -> {materialized, Event}
|
Event -> {materialized, Event}
|
||||||
end
|
end
|
||||||
end, Occurrences).
|
end, Occurrences).
|
||||||
|
|
||||||
|
%% ─── Административные функции (без проверки прав) ─────────────────
|
||||||
|
|
||||||
|
list_all_events(Filters) ->
|
||||||
|
Events = core_event:list_all(), % возвращает список, а не {ok, List}
|
||||||
|
Filtered = apply_filters(Events, Filters),
|
||||||
|
{ok, Filtered}.
|
||||||
|
|
||||||
|
get_event_admin(EventId) ->
|
||||||
|
core_event:get_by_id(EventId).
|
||||||
|
|
||||||
|
update_event_admin(EventId, Updates) ->
|
||||||
|
case core_event:get_by_id(EventId) of
|
||||||
|
{ok, _Event} ->
|
||||||
|
ValidUpdates = validate_updates(Updates, undefined),
|
||||||
|
core_event:update(EventId, ValidUpdates);
|
||||||
|
Error ->
|
||||||
|
Error
|
||||||
|
end.
|
||||||
|
|
||||||
|
delete_event_admin(EventId) ->
|
||||||
|
core_event:delete(EventId).
|
||||||
|
|
||||||
|
%% Применяет фильтры from/to к списку событий
|
||||||
|
apply_filters(Events, []) ->
|
||||||
|
Events;
|
||||||
|
apply_filters(Events, [{from, From} | Rest]) ->
|
||||||
|
apply_filters(
|
||||||
|
[E || E <- Events, E#event.start_time >= From],
|
||||||
|
Rest
|
||||||
|
);
|
||||||
|
apply_filters(Events, [{to, To} | Rest]) ->
|
||||||
|
apply_filters(
|
||||||
|
[E || E <- Events, E#event.start_time =< To],
|
||||||
|
Rest
|
||||||
|
);
|
||||||
|
apply_filters(Events, [_ | Rest]) ->
|
||||||
|
apply_filters(Events, Rest).
|
||||||
@@ -109,6 +109,7 @@ test() ->
|
|||||||
}),
|
}),
|
||||||
{ok, {{_, 201, _}, _, TicketResp}} = httpc:request(post, {AdminURL ++ "/v1/admin/tickets", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}], "application/json", TicketBody}, [], []),
|
{ok, {{_, 201, _}, _, TicketResp}} = httpc:request(post, {AdminURL ++ "/v1/admin/tickets", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}], "application/json", TicketBody}, [], []),
|
||||||
#{<<"id">> := TicketId} = jsx:decode(list_to_binary(TicketResp), [return_maps]),
|
#{<<"id">> := TicketId} = jsx:decode(list_to_binary(TicketResp), [return_maps]),
|
||||||
|
ct:pal(" OK (TicketId: ~p)~n", [TicketId]),
|
||||||
ct:pal("OK~n"),
|
ct:pal("OK~n"),
|
||||||
|
|
||||||
%% TEST 13: Get ticket by ID
|
%% TEST 13: Get ticket by ID
|
||||||
@@ -260,5 +261,76 @@ test() ->
|
|||||||
{ok, {{_, 405, _}, _, _}} = httpc:request(post, {AdminURL ++ "/v1/admin/reviews", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}], "application/json", <<"{}">>}, [], []),
|
{ok, {{_, 405, _}, _, _}} = httpc:request(post, {AdminURL ++ "/v1/admin/reviews", [{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}], "application/json", <<"{}">>}, [], []),
|
||||||
ct:pal("OK~n"),
|
ct:pal("OK~n"),
|
||||||
|
|
||||||
|
%% ========================================================
|
||||||
|
%% Admin Events tests
|
||||||
|
%% ========================================================
|
||||||
|
|
||||||
|
FutureDate = api_SUITE:future_date(),
|
||||||
|
FutureDateStr = binary_to_list(FutureDate),
|
||||||
|
|
||||||
|
%% TEST 28: List all events (GET /v1/admin/events)
|
||||||
|
ct:pal(" TEST 28: List all events... "),
|
||||||
|
{ok, {{_, 200, _}, _, EventsListResp}} =
|
||||||
|
httpc:request(get, {AdminURL ++ "/v1/admin/events",
|
||||||
|
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||||
|
[], []),
|
||||||
|
EventsList = jsx:decode(list_to_binary(EventsListResp), [return_maps]),
|
||||||
|
true = is_list(EventsList),
|
||||||
|
ct:pal(" OK (count: ~p)~n", [length(EventsList)]),
|
||||||
|
|
||||||
|
%% TEST 29: List events with date filters
|
||||||
|
ct:pal(" TEST 29: List events with date filters... "),
|
||||||
|
FilterEventsURL = AdminURL ++ "/v1/admin/events?from=" ++ FutureDateStr ++
|
||||||
|
"&to=" ++ FutureDateStr,
|
||||||
|
{ok, {{_, 200, _}, _, FilteredEventsResp}} =
|
||||||
|
httpc:request(get, {FilterEventsURL,
|
||||||
|
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||||
|
[], []),
|
||||||
|
FilteredEventsList = jsx:decode(list_to_binary(FilteredEventsResp), [return_maps]),
|
||||||
|
true = is_list(FilteredEventsList),
|
||||||
|
ct:pal(" OK (filtered count: ~p)~n", [length(FilteredEventsList)]),
|
||||||
|
|
||||||
|
%% TEST 30: Get event by ID (GET /v1/admin/events/:id)
|
||||||
|
ct:pal(" TEST 30: Get event by ID... "),
|
||||||
|
{ok, {{_, 200, _}, _, EventByIdResp}} =
|
||||||
|
httpc:request(get, {AdminURL ++ "/v1/admin/events/" ++ binary_to_list(EventId),
|
||||||
|
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||||
|
[], []),
|
||||||
|
#{<<"id">> := EventId} = jsx:decode(list_to_binary(EventByIdResp), [return_maps]),
|
||||||
|
ct:pal(" OK (id: ~s)~n", [binary_to_list(EventId)]),
|
||||||
|
|
||||||
|
%% TEST 31: Update event by ID (PUT /v1/admin/events/:id)
|
||||||
|
ct:pal(" TEST 31: Update event by ID... "),
|
||||||
|
UpdateEventBody = jsx:encode(#{
|
||||||
|
<<"title">> => <<"Updated by admin">>,
|
||||||
|
<<"description">> => <<"Admin test update">>
|
||||||
|
}),
|
||||||
|
{ok, {{_, 200, _}, _, UpdateEventResp}} =
|
||||||
|
httpc:request(put, {AdminURL ++ "/v1/admin/events/" ++ binary_to_list(EventId),
|
||||||
|
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}],
|
||||||
|
"application/json", UpdateEventBody},
|
||||||
|
[], []),
|
||||||
|
#{<<"title">> := <<"Updated by admin">>} =
|
||||||
|
jsx:decode(list_to_binary(UpdateEventResp), [return_maps]),
|
||||||
|
ct:pal(" OK~n"),
|
||||||
|
|
||||||
|
%% TEST 32: Delete event by ID (DELETE /v1/admin/events/:id)
|
||||||
|
ct:pal(" TEST 32: Delete event by ID... "),
|
||||||
|
{ok, {{_, 200, _}, _, DeleteResp}} =
|
||||||
|
httpc:request(delete, {AdminURL ++ "/v1/admin/events/" ++ binary_to_list(EventId),
|
||||||
|
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}]},
|
||||||
|
[], []),
|
||||||
|
#{<<"status">> := <<"deleted">>} = jsx:decode(list_to_binary(DeleteResp), [return_maps]),
|
||||||
|
ct:pal(" OK (status deleted)~n"),
|
||||||
|
|
||||||
|
%% TEST 33: Method not allowed (POST /v1/admin/events → 405)
|
||||||
|
ct:pal(" TEST 33: POST method not allowed... "),
|
||||||
|
{ok, {{_, 405, _}, _, _}} =
|
||||||
|
httpc:request(post, {AdminURL ++ "/v1/admin/events",
|
||||||
|
[{"Authorization", "Bearer " ++ binary_to_list(AdminToken)}],
|
||||||
|
"application/json", <<"{}">>},
|
||||||
|
[], []),
|
||||||
|
ct:pal("OK~n"),
|
||||||
|
|
||||||
ct:pal("~n✅ Admin API tests passed!~n"),
|
ct:pal("~n✅ Admin API tests passed!~n"),
|
||||||
{?MODULE, ok}.
|
{?MODULE, ok}.
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
-export([unique_email/1, register_and_login/2, create_calendar/2, create_event/3]).
|
-export([unique_email/1, register_and_login/2, create_calendar/2, create_event/3]).
|
||||||
-export([get_admin_token/0, get_admin_id/0, get_user_token/0, get_user_id/0, get_admin_url/0, get_base_url/0, get_admin_ws_url/0, get_base_ws_url/0]).
|
-export([get_admin_token/0, get_admin_id/0, get_user_token/0, get_user_id/0, get_admin_url/0, get_base_url/0, get_admin_ws_url/0, get_base_ws_url/0]).
|
||||||
-export([wait_for_server/0]).
|
-export([wait_for_server/0]).
|
||||||
|
-export([format_datetime/1]).
|
||||||
|
|
||||||
-define(BASE_URL, base_url()).
|
-define(BASE_URL, base_url()).
|
||||||
-define(ADMIN_URL, admin_base_url()).
|
-define(ADMIN_URL, admin_base_url()).
|
||||||
@@ -260,3 +261,9 @@ wait_for_server(Attempts) ->
|
|||||||
{ok, {{_, 200, _}, _, _}} -> ok;
|
{ok, {{_, 200, _}, _, _}} -> ok;
|
||||||
_ -> timer:sleep(1000), wait_for_server(Attempts - 1)
|
_ -> timer:sleep(1000), wait_for_server(Attempts - 1)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
format_datetime({{Year, Month, Day}, {Hour, Minute, Second}}) ->
|
||||||
|
iolist_to_binary(
|
||||||
|
io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ",
|
||||||
|
[Year, Month, Day, Hour, Minute, Second])
|
||||||
|
).
|
||||||
@@ -17,6 +17,7 @@ test() ->
|
|||||||
stacktrace => <<"Something broke">>},
|
stacktrace => <<"Something broke">>},
|
||||||
Token),
|
Token),
|
||||||
<<"id">>),
|
<<"id">>),
|
||||||
|
ct:pal(" OK (TicketId: ~p)~n", [TicketId]),
|
||||||
io:format("OK~n"),
|
io:format("OK~n"),
|
||||||
|
|
||||||
%% TEST 2: Get my tickets (user)
|
%% TEST 2: Get my tickets (user)
|
||||||
|
|||||||
Reference in New Issue
Block a user