Files
EventHubBack/src/handlers/handler_calendars.erl

194 lines
7.9 KiB
Erlang
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
%%%-------------------------------------------------------------------
%%% @doc Обработчик маршрута `/v1/calendars`.
%%%
%%% POST создание нового календаря (требуется подписка для commercial).
%%% GET получение списка календарей пользователя.
%%% @end
%%%-------------------------------------------------------------------
-module(handler_calendars).
-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(Req0, _State) ->
case cowboy_req:method(Req0) of
<<"POST">> -> create_calendar(Req0);
<<"GET">> -> list_calendars(Req0);
_ -> handler_utils:send_error(Req0, 405, <<"Method not allowed">>)
end.
%%% Swagger metadata
-spec trails() -> [map()].
trails() ->
[
#{ % POST
path => <<"/v1/calendars">>,
method => <<"POST">>,
description => <<"Create a new calendar">>,
tags => [<<"Calendars">>],
requestBody => #{
required => true,
content => #{<<"application/json">> => #{schema => calendar_create_schema()}}
},
responses => #{
201 => #{description => <<"Calendar created">>},
400 => #{description => <<"Missing required fields or invalid JSON">>},
402 => #{description => <<"Subscription required for commercial calendar">>},
403 => #{description => <<"User account is not active">>}
}
},
#{ % GET
path => <<"/v1/calendars">>,
method => <<"GET">>,
description => <<"List calendars of current user">>,
tags => [<<"Calendars">>],
responses => #{
200 => #{
description => <<"Array of calendars">>,
content => #{<<"application/json">> => #{schema => #{
type => array,
items => calendar_schema()
}}}
}
}
}
].
-spec calendar_schema() -> map().
calendar_schema() ->
#{
type => object,
properties => #{
id => #{type => string},
owner_id => #{type => string},
title => #{type => string},
description => #{type => string},
short_name => #{type => string, nullable => true},
category => #{type => string, nullable => true},
color => #{type => string, nullable => true},
image_url => #{type => string, nullable => true},
settings => #{type => object, nullable => true},
tags => #{type => array, items => #{type => string}},
type => #{type => string, enum => [<<"personal">>, <<"commercial">>]},
confirmation => #{type => string, description => <<"auto, manual, or {timeout, N}">>},
rating_avg => #{type => number, format => float},
rating_count => #{type => integer},
status => #{type => string, enum => [<<"active">>, <<"frozen">>, <<"deleted">>]},
reason => #{type => string, nullable => true},
created_at => #{type => string, format => <<"date-time">>},
updated_at => #{type => string, format => <<"date-time">>}
}
}.
-spec calendar_create_schema() -> map().
calendar_create_schema() ->
#{
type => object,
required => [<<"title">>],
properties => #{
title => #{type => string},
description => #{type => string},
confirmation => #{type => string, description => <<"auto, manual, or {timeout, N}">>},
tags => #{type => array, items => #{type => string}},
type => #{type => string, enum => [<<"personal">>, <<"commercial">>]}
}
}.
%%%===================================================================
%%% HTTP-методы
%%%===================================================================
%% @doc POST /v1/calendars — создание календаря.
-spec create_calendar(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}.
create_calendar(Req) ->
case handler_utils:auth_user(Req) of
{ok, UserId, Req1} ->
{ok, Body, Req2} = cowboy_req:read_body(Req1),
try jsx:decode(Body, [return_maps]) of
Decoded when is_map(Decoded) ->
case Decoded of
#{<<"title">> := Title} ->
Description = maps:get(<<"description">>, Decoded, <<"">>),
Confirmation = parse_confirmation(maps:get(<<"confirmation">>, Decoded, <<"manual">>)),
Tags = maps:get(<<"tags">>, Decoded, []),
Type = parse_type(maps:get(<<"type">>, Decoded, <<"personal">>)),
case Type of
commercial ->
case logic_subscription:can_create_commercial_calendar(UserId) of
true -> ok;
false ->
handler_utils:send_error(Req2, 402, <<"Subscription required for commercial calendar">>),
throw(stop)
end;
personal -> ok
end,
case logic_calendar:create_calendar(UserId, Title, Description, Confirmation) of
{ok, Calendar} ->
Updates = [{tags, Tags}, {type, Type}],
core_calendar:update(Calendar#calendar.id, Updates),
{ok, Updated} = core_calendar:get_by_id(Calendar#calendar.id),
Response = calendar_to_json(Updated),
handler_utils:send_json(Req2, 201, Response);
{error, user_inactive} ->
handler_utils:send_error(Req2, 403, <<"User account is not active">>);
{error, _} ->
handler_utils:send_error(Req2, 500, <<"Internal server error">>)
end;
_ ->
handler_utils:send_error(Req2, 400, <<"Missing required field: title">>)
end;
_ ->
handler_utils:send_error(Req2, 400, <<"Invalid JSON">>)
catch
throw:stop -> ok;
_:_ -> handler_utils:send_error(Req2, 400, <<"Invalid JSON format">>)
end;
{error, Code, Message, Req1} ->
handler_utils:send_error(Req1, Code, Message)
end.
%% @doc GET /v1/calendars — список календарей пользователя.
-spec list_calendars(cowboy_req:req()) -> {ok, binary(), cowboy_req:req()}.
list_calendars(Req) ->
case handler_utils:auth_user(Req) of
{ok, UserId, Req1} ->
case logic_calendar:list_calendars(UserId) of
{ok, Calendars} ->
Response = [calendar_to_json(C) || C <- Calendars],
handler_utils:send_json(Req1, 200, Response);
{error, _} ->
handler_utils:send_error(Req1, 500, <<"Internal server error">>)
end;
{error, Code, Message, Req1} ->
handler_utils:send_error(Req1, Code, Message)
end.
%%%===================================================================
%%% Внутренние функции
%%%===================================================================
-spec calendar_to_json(#calendar{}) -> map().
calendar_to_json(Calendar) ->
Base = handler_utils:calendar_to_json(Calendar),
Base#{confirmation => confirmation_to_json(Calendar#calendar.confirmation)}.
-spec confirmation_to_json(auto | manual | {timeout, integer()}) -> binary() | map().
confirmation_to_json(auto) -> <<"auto">>;
confirmation_to_json(manual) -> <<"manual">>;
confirmation_to_json({timeout, N}) -> #{<<"timeout">> => N}.
-spec parse_confirmation(binary() | map()) -> auto | manual | {timeout, integer()}.
parse_confirmation(<<"auto">>) -> auto;
parse_confirmation(<<"manual">>) -> manual;
parse_confirmation(#{<<"timeout">> := N}) when is_integer(N), N > 0 -> {timeout, N};
parse_confirmation(_) -> manual.
-spec parse_type(binary()) -> personal | commercial.
parse_type(<<"personal">>) -> personal;
parse_type(<<"commercial">>) -> commercial;
parse_type(_) -> personal.