%%%------------------------------------------------------------------- %%% @doc Accepted shares on a calendar + revoke / rights / self-mirror. %%% %%% GET/DELETE/PUT /v1/calendars/:id/shares[/:user_id] %%% PUT /v1/user/shares/:calendar_id body: {mirror_to_default} %%% @end %%%------------------------------------------------------------------- -module(handler_calendar_shares). -behaviour(cowboy_handler). -export([init/2, trails/0]). -include("records.hrl"). init(Req, Opts) -> handle(Req, Opts). trails() -> IdParam = #{name => <<"id">>, in => <<"path">>, required => true, schema => #{type => string}}, UserParam = #{name => <<"user_id">>, in => <<"path">>, required => true, schema => #{type => string}}, CalParam = #{name => <<"calendar_id">>, in => <<"path">>, required => true, schema => #{type => string}}, [ #{path => <<"/v1/calendars/:id/shares">>, method => <<"GET">>, description => <<"List calendar shares">>, tags => [<<"Calendars">>], parameters => [IdParam], responses => #{200 => #{description => <<"OK">>}}}, #{path => <<"/v1/calendars/:id/shares/:user_id">>, method => <<"DELETE">>, description => <<"Revoke share grant">>, tags => [<<"Calendars">>], parameters => [IdParam, UserParam], responses => #{200 => #{description => <<"OK">>}}}, #{path => <<"/v1/calendars/:id/shares/:user_id">>, method => <<"PUT">>, description => <<"Update share rights">>, tags => [<<"Calendars">>], parameters => [IdParam, UserParam], responses => #{200 => #{description => <<"OK">>}}}, #{path => <<"/v1/user/shares/:calendar_id">>, method => <<"PUT">>, description => <<"Update own mirror_to_default">>, tags => [<<"Users">>], parameters => [CalParam], responses => #{200 => #{description => <<"OK">>}}} ]. handle(Req, _Opts) -> Method = cowboy_req:method(Req), Path = cowboy_req:path(Req), case Path of <<"/v1/user/shares/", _/binary>> -> case Method of <<"PUT">> -> update_my_mirror(Req); _ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>) end; _ -> UserId = cowboy_req:binding(user_id, Req), case {Method, UserId} of {<<"GET">>, undefined} -> list_shares(Req); {<<"DELETE">>, Id} when is_binary(Id) -> revoke_share(Req); {<<"PUT">>, Id} when is_binary(Id) -> update_rights(Req); _ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>) end end. list_shares(Req) -> case handler_utils:auth_user(Req) of {ok, ActorId, Req1} -> CalendarId = cowboy_req:binding(id, Req1), case logic_calendar_share:list(ActorId, CalendarId) of {ok, List} -> handler_utils:send_json(Req1, 200, [logic_calendar_share: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. revoke_share(Req) -> case handler_utils:auth_user(Req) of {ok, ActorId, Req1} -> CalendarId = cowboy_req:binding(id, Req1), TargetUserId = cowboy_req:binding(user_id, Req1), case logic_calendar_share:revoke(ActorId, CalendarId, TargetUserId) of ok -> handler_utils:send_json(Req1, 200, #{ok => true}); {error, not_found} -> handler_utils:send_error(Req1, 404, <<"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. update_rights(Req) -> case handler_utils:auth_user(Req) of {ok, ActorId, Req1} -> CalendarId = cowboy_req:binding(id, Req1), TargetUserId = cowboy_req:binding(user_id, Req1), {ok, Body, Req2} = cowboy_req:read_body(Req1), try jsx:decode(Body, [return_maps]) of #{<<"rights">> := RightsBin} when is_binary(RightsBin) -> case binary_to_existing_atom_safe(RightsBin) of invalid -> handler_utils:send_error(Req2, 400, <<"Bad request">>); Rights -> case logic_calendar_share:update_rights(ActorId, CalendarId, TargetUserId, Rights) of {ok, Share} -> handler_utils:send_json(Req2, 200, logic_calendar_share:to_json(Share)); {error, Reason} -> map_share_error(Req2, Reason) end end; _ -> handler_utils:send_error(Req2, 400, <<"rights required">>) catch _:_ -> handler_utils:send_error(Req2, 400, <<"Invalid JSON">>) end; {error, Code, Message, Req1} -> handler_utils:send_error(Req1, Code, Message) end. update_my_mirror(Req) -> case handler_utils:auth_user(Req) of {ok, UserId, Req1} -> CalendarId = cowboy_req:binding(calendar_id, Req1), {ok, Body, Req2} = cowboy_req:read_body(Req1), try jsx:decode(Body, [return_maps]) of #{<<"mirror_to_default">> := Mirror} when is_boolean(Mirror) -> case logic_calendar_share:update_my_mirror(UserId, CalendarId, Mirror) of {ok, Share} -> handler_utils:send_json(Req2, 200, logic_calendar_share:to_json(Share)); {error, Reason} -> map_share_error(Req2, Reason) end; _ -> handler_utils:send_error(Req2, 400, <<"mirror_to_default required">>) catch _:_ -> handler_utils:send_error(Req2, 400, <<"Invalid JSON">>) end; {error, Code, Message, Req1} -> handler_utils:send_error(Req1, Code, Message) end. binary_to_existing_atom_safe(<<"read">>) -> read; binary_to_existing_atom_safe(<<"write">>) -> write; binary_to_existing_atom_safe(<<"admin">>) -> admin; binary_to_existing_atom_safe(_) -> invalid. map_share_error(Req, not_found) -> handler_utils:send_error(Req, 404, <<"Not found">>); map_share_error(Req, access_denied) -> handler_utils:send_error(Req, 403, <<"Access denied">>); map_share_error(Req, bad_request) -> handler_utils:send_error(Req, 400, <<"Bad request">>); map_share_error(Req, invalid) -> handler_utils:send_error(Req, 400, <<"Bad request">>); map_share_error(Req, _) -> handler_utils:send_error(Req, 500, <<"Internal server error">>).