%%%------------------------------------------------------------------- %%% @doc CRUD специалистов календаря. %%% %%% GET /v1/calendars/:id/specialists %%% POST /v1/calendars/:id/specialists %%% PUT /v1/calendars/:id/specialists/:user_id %%% DELETE /v1/calendars/:id/specialists/:user_id %%% @end %%%------------------------------------------------------------------- -module(handler_calendar_specialists). -behaviour(cowboy_handler). -export([init/2]). -export([trails/0]). -include("records.hrl"). -spec init(cowboy_req:req(), any()) -> {ok, cowboy_req:req(), any()}. init(Req, Opts) -> handle(Req, Opts). -spec trails() -> [map()]. trails() -> SpecSchema = #{ type => object, properties => #{ id => #{type => string}, calendar_id => #{type => string}, user_id => #{type => string}, name => #{type => string}, specialization => #{type => array, items => #{type => string}}, status => #{type => string, enum => [<<"active">>, <<"inactive">>]} } }, IdParam = #{ name => <<"id">>, in => <<"path">>, required => true, schema => #{type => string} }, UserParam = #{ name => <<"user_id">>, in => <<"path">>, required => true, schema => #{type => string} }, [ #{ path => <<"/v1/calendars/:id/specialists">>, method => <<"GET">>, description => <<"List calendar specialists">>, tags => [<<"Calendars">>], parameters => [IdParam], responses => #{ 200 => #{description => <<"OK">>, content => #{<<"application/json">> => #{schema => #{type => array, items => SpecSchema}}}} } }, #{ path => <<"/v1/calendars/:id/specialists">>, method => <<"POST">>, description => <<"Add specialist (owner, commercial)">>, tags => [<<"Calendars">>], parameters => [IdParam], responses => #{201 => #{description => <<"Created">>}} }, #{ path => <<"/v1/calendars/:id/specialists/:user_id">>, method => <<"PUT">>, description => <<"Update specialist">>, tags => [<<"Calendars">>], parameters => [IdParam, UserParam], responses => #{200 => #{description => <<"Updated">>}} }, #{ path => <<"/v1/calendars/:id/specialists/:user_id">>, method => <<"DELETE">>, description => <<"Remove specialist">>, tags => [<<"Calendars">>], parameters => [IdParam, UserParam], responses => #{200 => #{description => <<"Deleted">>}} } ]. -spec handle(cowboy_req:req(), any()) -> {ok, cowboy_req:req(), any()}. handle(Req, _Opts) -> Method = cowboy_req:method(Req), case {Method, has_user_binding(Req)} of {<<"GET">>, false} -> list_specialists(Req); {<<"POST">>, false} -> add_specialist(Req); {<<"PUT">>, true} -> update_specialist(Req); {<<"DELETE">>, true} -> remove_specialist(Req); _ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>) end. has_user_binding(Req) -> cowboy_req:binding(user_id, Req) =/= undefined. list_specialists(Req) -> case handler_utils:auth_user_optional(Req) of {ok, UserId, Req1} -> CalendarId = cowboy_req:binding(id, Req1), case logic_calendar_specialist:list(UserId, CalendarId) of {ok, List} -> handler_utils:send_json(Req1, 200, [logic_calendar_specialist:to_json(S) || S <- List]); {error, not_found} -> handler_utils:send_error(Req1, 404, <<"Calendar not found">>); {error, access_denied} -> handler_utils:send_error(Req1, 403, <<"Access denied">>); {error, _} -> handler_utils:send_error(Req1, 500, <<"Internal server error">>) end; {error, Code, Message, Req1} -> handler_utils:send_error(Req1, Code, Message) end. add_specialist(Req) -> case handler_utils:auth_user(Req) of {ok, OwnerId, Req1} -> CalendarId = cowboy_req:binding(id, Req1), {ok, Body, Req2} = cowboy_req:read_body(Req1), try jsx:decode(Body, [return_maps]) of #{<<"user_id">> := SpecUserId} = Map when is_binary(SpecUserId) -> Name = maps:get(<<"name">>, Map, <<>>), Specs = maps:get(<<"specialization">>, Map, []), Specs2 = case is_list(Specs) of true -> Specs; false -> [] end, case logic_calendar_specialist:add(OwnerId, CalendarId, SpecUserId, Name, Specs2) of {ok, Rec} -> handler_utils:send_json(Req2, 201, logic_calendar_specialist:to_json(Rec)); {error, not_found} -> handler_utils:send_error(Req2, 404, <<"Calendar not found">>); {error, access_denied} -> handler_utils:send_error(Req2, 403, <<"Access denied">>); {error, not_commercial} -> handler_utils:send_error(Req2, 400, <<"Calendar is not commercial">>); {error, user_not_found} -> handler_utils:send_error(Req2, 404, <<"User not found">>); {error, already_exists} -> handler_utils:send_error(Req2, 409, <<"Specialist already exists">>); {error, _} -> handler_utils:send_error(Req2, 500, <<"Internal server error">>) end; _ -> handler_utils:send_error(Req2, 400, <<"user_id required">>) catch _:_ -> handler_utils:send_error(Req2, 400, <<"Invalid JSON">>) end; {error, Code, Message, Req1} -> handler_utils:send_error(Req1, Code, Message) end. update_specialist(Req) -> case handler_utils:auth_user(Req) of {ok, OwnerId, Req1} -> CalendarId = cowboy_req:binding(id, Req1), SpecUserId = cowboy_req:binding(user_id, Req1), {ok, Body, Req2} = cowboy_req:read_body(Req1), try jsx:decode(Body, [return_maps]) of Map when is_map(Map) -> Updates = parse_updates(Map), case logic_calendar_specialist:update(OwnerId, CalendarId, SpecUserId, Updates) of {ok, Rec} -> handler_utils:send_json(Req2, 200, logic_calendar_specialist:to_json(Rec)); {error, not_found} -> handler_utils:send_error(Req2, 404, <<"Not found">>); {error, access_denied} -> handler_utils:send_error(Req2, 403, <<"Access denied">>); {error, not_commercial} -> handler_utils:send_error(Req2, 400, <<"Calendar is not commercial">>); {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">>) end; {error, Code, Message, Req1} -> handler_utils:send_error(Req1, Code, Message) end. remove_specialist(Req) -> case handler_utils:auth_user(Req) of {ok, OwnerId, Req1} -> CalendarId = cowboy_req:binding(id, Req1), SpecUserId = cowboy_req:binding(user_id, Req1), case logic_calendar_specialist:remove(OwnerId, CalendarId, SpecUserId) of ok -> handler_utils:send_json(Req1, 200, #{status => <<"deleted">>}); {error, not_found} -> handler_utils:send_error(Req1, 404, <<"Not found">>); {error, owner_specialist_protected} -> handler_utils:send_error(Req1, 403, <<"Owner specialist cannot be removed">>); {error, access_denied} -> handler_utils:send_error(Req1, 403, <<"Access denied">>); {error, not_commercial} -> handler_utils:send_error(Req1, 400, <<"Calendar is not commercial">>); {error, _} -> handler_utils:send_error(Req1, 500, <<"Internal server error">>) end; {error, Code, Message, Req1} -> handler_utils:send_error(Req1, Code, Message) end. parse_updates(Map) -> lists:filtermap(fun ({<<"name">>, V}) when is_binary(V) -> {true, {name, V}}; ({<<"specialization">>, V}) when is_list(V) -> {true, {specialization, V}}; ({<<"status">>, <<"active">>}) -> {true, {status, active}}; ({<<"status">>, <<"inactive">>}) -> {true, {status, inactive}}; (_) -> false end, maps:to_list(Map)).