Files
EventHubBack/src/handlers/handler_calendar_by_id.erl
T
aleksey 3d7ae0d372
CI / test (push) Successful in 7m28s
CI / deploy-ift (push) Successful in 2m46s
CI / e2e-ift (push) Successful in 1m26s
CI / deploy-stage (push) Successful in 2m4s
CI / e2e-stage (push) Successful in 2m4s
fix(api): resolve public calendar GET by unique short_name.
2026-08-14 15:00:41 +03:00

217 lines
8.6 KiB
Erlang
Executable File

%%%-------------------------------------------------------------------
%%% @doc Обработчик конкретного календаря (клиентский API).
%%% GET – получить информацию о календаре.
%%% PUT – обновить календарь (владельцем).
%%% DELETE – удалить календарь (владельцем).
%%% @end
%%%-------------------------------------------------------------------
-module(handler_calendar_by_id).
-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_calendar(Req);
<<"PUT">> -> update_calendar(Req);
<<"DELETE">> -> delete_calendar(Req);
_ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>)
end.
%%% Swagger metadata
-spec trails() -> [map()].
trails() ->
BaseParams = [#{
name => <<"id">>,
in => <<"path">>,
description => <<"Calendar ID">>,
required => true,
schema => #{type => string}
}],
[
#{ % GET
path => <<"/v1/calendars/:id">>,
method => <<"GET">>,
description => <<"Get calendar by ID">>,
tags => [<<"Calendars">>],
parameters => BaseParams,
responses => #{
200 => #{
description => <<"Calendar details">>,
content => #{<<"application/json">> => #{schema => calendar_schema()}}
}
}
},
#{ % PUT
path => <<"/v1/calendars/:id">>,
method => <<"PUT">>,
description => <<"Update calendar">>,
tags => [<<"Calendars">>],
parameters => BaseParams,
requestBody => #{
required => true,
content => #{<<"application/json">> => #{schema => calendar_update_schema()}}
},
responses => #{
200 => #{description => <<"Calendar updated">>}
}
},
#{ % DELETE
path => <<"/v1/calendars/:id">>,
method => <<"DELETE">>,
description => <<"Delete calendar">>,
tags => [<<"Calendars">>],
parameters => BaseParams,
responses => #{
200 => #{description => <<"Calendar deleted">>}
}
}
].
-spec calendar_schema() -> map().
calendar_schema() ->
#{
type => object,
properties => #{
id => #{type => string},
owner_id => #{type => string},
title => #{type => string},
description => #{type => string},
type => #{type => string, enum => [<<"personal">>, <<"commercial">>]},
confirmation => #{type => string, enum => [<<"auto">>, <<"manual">>]},
tags => #{type => array, items => #{type => string}},
settings => #{type => object},
rating_avg => #{type => number, format => float},
rating_count => #{type => integer},
status => #{type => string},
created_at => #{type => string, format => <<"date-time">>},
updated_at => #{type => string, format => <<"date-time">>}
}
}.
-spec calendar_update_schema() -> map().
calendar_update_schema() ->
#{
type => object,
properties => #{
title => #{type => string},
description => #{type => string},
type => #{type => string},
confirmation => #{type => string},
tags => #{type => array, items => #{type => string}},
short_name => #{type => string},
category => #{type => string},
color => #{type => string},
image_url => #{type => string},
settings => #{type => object, nullable => true}
}
}.
%%% Internal functions
get_calendar(Req) ->
case handler_utils:auth_user_optional(Req) of
{ok, UserId, Req1} ->
CalendarId = cowboy_req:binding(id, Req1),
case logic_calendar:get_calendar(UserId, CalendarId) of
{ok, Calendar} ->
Json0 = handler_utils:calendar_to_json(Calendar),
Following = UserId =/= <<>> andalso
logic_calendar_follow:is_following(UserId, Calendar#calendar.id),
handler_utils:send_json(Req1, 200, Json0#{following => Following});
{error, access_denied} ->
handler_utils:send_error(Req1, 403, <<"Access denied">>);
{error, not_found} ->
handler_utils:send_error(Req1, 404, <<"Calendar not found">>);
{error, _} ->
handler_utils:send_error(Req1, 500, <<"Internal server error">>)
end;
{error, Code, Message, Req1} ->
handler_utils:send_error(Req1, Code, Message)
end.
update_calendar(Req) ->
case handler_utils:auth_user(Req) of
{ok, UserId, Req1} ->
CalendarId = 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) ->
Updates0 = maps:to_list(UpdatesMap),
Updates = convert_calendar_fields(Updates0),
case logic_calendar:update_calendar(UserId, CalendarId, Updates) of
{ok, Calendar} ->
Json0 = handler_utils:calendar_to_json(Calendar),
Following = logic_calendar_follow:is_following(UserId, CalendarId),
handler_utils:send_json(Req2, 200, Json0#{following => Following});
{error, access_denied} ->
handler_utils:send_error(Req2, 403, <<"Access denied">>);
{error, subscription_required} ->
handler_utils:send_error(Req2, 402, <<"Subscription required for commercial calendar">>);
{error, default_calendar} ->
handler_utils:send_error(Req2, 403, <<"default_calendar">>);
{error, not_found} ->
handler_utils:send_error(Req2, 404, <<"Calendar not found">>);
{error, {invalid_settings, Key}} when is_binary(Key) ->
handler_utils:send_error(Req2, 400,
<<"Invalid settings.", Key/binary>>);
{error, _} ->
handler_utils:send_error(Req2, 500, <<"Internal server error">>)
end;
_ ->
handler_utils:send_error(Req2, 400, <<"Invalid JSON">>)
catch
_:_ -> handler_utils:send_error(Req2, 400, <<"Invalid JSON format">>)
end;
{error, Code, Message, Req1} ->
handler_utils:send_error(Req1, Code, Message)
end.
delete_calendar(Req) ->
case handler_utils:auth_user(Req) of
{ok, UserId, Req1} ->
CalendarId = cowboy_req:binding(id, Req1),
case logic_calendar:delete_calendar(UserId, CalendarId) of
{ok, _} ->
handler_utils:send_json(Req1, 200, #{status => <<"deleted">>});
{error, default_calendar} ->
handler_utils:send_error(Req1, 403, <<"default_calendar">>);
{error, access_denied} ->
handler_utils:send_error(Req1, 403, <<"Access denied">>);
{error, not_found} ->
handler_utils:send_error(Req1, 404, <<"Calendar not found">>);
{error, _} ->
handler_utils:send_error(Req1, 500, <<"Internal server error">>)
end;
{error, Code, Message, Req1} ->
handler_utils:send_error(Req1, Code, Message)
end.
%% @private Преобразует бинарные ключи и значения в атомы для обновлений календаря.
-spec convert_calendar_fields([{binary(), term()}]) -> [{atom(), term()}].
convert_calendar_fields(Updates) ->
lists:map(fun convert_field/1, Updates).
-spec convert_field({binary(), term()}) -> {atom(), term()}.
convert_field({<<"title">>, Val}) -> {title, Val};
convert_field({<<"description">>, Val}) -> {description, Val};
convert_field({<<"type">>, <<"personal">>}) -> {type, personal};
convert_field({<<"type">>, <<"commercial">>}) -> {type, commercial};
convert_field({<<"type">>, Val}) -> {type, Val};
convert_field({<<"confirmation">>, <<"auto">>}) -> {confirmation, auto};
convert_field({<<"confirmation">>, <<"manual">>}) -> {confirmation, manual};
convert_field({<<"confirmation">>, #{<<"timeout">> := N}}) when is_integer(N), N > 0 ->
{confirmation, {timeout, N}};
convert_field({<<"confirmation">>, Val}) -> {confirmation, Val};
convert_field({<<"tags">>, Val}) -> {tags, Val};
convert_field({<<"short_name">>, Val}) -> {short_name, Val};
convert_field({<<"category">>, Val}) -> {category, Val};
convert_field({<<"color">>, Val}) -> {color, Val};
convert_field({<<"image_url">>, Val}) -> {image_url, Val};
convert_field({<<"settings">>, Val}) when is_map(Val) -> {settings, Val};
convert_field(Other) -> Other.