./ct_report/coverage/mongoose_client_api_contacts.COVER.html

1 -module(mongoose_client_api_contacts).
2
3 -behaviour(mongoose_client_api).
4 -export([routes/0]).
5
6 -behaviour(cowboy_rest).
7 -export([trails/0,
8 init/2,
9 is_authorized/2,
10 content_types_provided/2,
11 content_types_accepted/2,
12 allowed_methods/2,
13 to_json/2,
14 from_json/2,
15 delete_resource/2]).
16
17 -ignore_xref([from_json/2, to_json/2, trails/0]).
18
19 -import(mongoose_client_api, [parse_body/1, try_handle_request/3, throw_error/2]).
20
21 -type req() :: cowboy_req:req().
22 -type state() :: map().
23
24 -spec routes() -> mongoose_http_handler:routes().
25 routes() ->
26 4 [{"/contacts/[:jid]", ?MODULE, #{}}].
27
28 trails() ->
29 4 mongoose_client_api_contacts_doc:trails().
30
31 -spec init(req(), state()) -> {cowboy_rest, req(), state()}.
32 init(Req, Opts) ->
33
:-(
mongoose_client_api:init(Req, Opts).
34
35 -spec is_authorized(req(), state()) -> {true | {false, iodata()}, req(), state()}.
36 is_authorized(Req, State) ->
37
:-(
mongoose_client_api:is_authorized(Req, State).
38
39 -spec content_types_provided(req(), state()) ->
40 {[{{binary(), binary(), '*'}, atom()}], req(), state()}.
41 content_types_provided(Req, State) ->
42
:-(
{[
43 {{<<"application">>, <<"json">>, '*'}, to_json}
44 ], Req, State}.
45
46 -spec content_types_accepted(req(), state()) ->
47 {[{{binary(), binary(), '*'}, atom()}], req(), state()}.
48 content_types_accepted(Req, State) ->
49
:-(
{[
50 {{<<"application">>, <<"json">>, '*'}, from_json}
51 ], Req, State}.
52
53 -spec allowed_methods(req(), state()) -> {[binary()], req(), state()}.
54 allowed_methods(Req, State) ->
55
:-(
{[<<"OPTIONS">>, <<"GET">>, <<"POST">>, <<"PUT">>, <<"DELETE">>],
56 Req, State}.
57
58 %% @doc Called for a method of type "GET"
59 -spec to_json(req(), state()) -> {iodata() | stop, req(), state()}.
60 to_json(Req, State) ->
61
:-(
try_handle_request(Req, State, fun handle_get/2).
62
63 %% @doc Called for a method of type "POST" or "PUT"
64 -spec from_json(req(), state()) -> {true | stop, req(), state()}.
65 from_json(Req, State) ->
66
:-(
F = case cowboy_req:method(Req) of
67
:-(
<<"POST">> -> fun handle_post/2;
68
:-(
<<"PUT">> -> fun handle_put/2
69 end,
70
:-(
try_handle_request(Req, State, F).
71
72 %% @doc Called for a method of type "DELETE"
73 -spec delete_resource(req(), state()) -> {true | stop, req(), state()}.
74 delete_resource(Req, State) ->
75
:-(
try_handle_request(Req, State, fun handle_delete/2).
76
77 %% Internal functions
78
79 handle_get(Req, State = #{jid := UserJid}) ->
80
:-(
Bindings = cowboy_req:bindings(Req),
81
:-(
assert_no_jid(Bindings),
82
:-(
{ok, Contacts} = mod_roster_api:list_contacts(UserJid),
83
:-(
{jiffy:encode(lists:map(fun roster_info/1, Contacts)), Req, State}.
84
85 handle_post(Req, State = #{jid := UserJid}) ->
86
:-(
Args = parse_body(Req),
87
:-(
ContactJid = get_jid(Args),
88
:-(
case mod_roster_api:add_contact(UserJid, ContactJid, <<>>, []) of
89 {user_not_exist, Reason} ->
90
:-(
throw_error(not_found, Reason);
91 {ok, _} ->
92
:-(
{true, Req, State}
93 end.
94
95 handle_put(Req, State = #{jid := UserJid}) ->
96
:-(
Bindings = cowboy_req:bindings(Req),
97
:-(
ContactJid = get_jid(Bindings),
98
:-(
Args = parse_body(Req),
99
:-(
Action = get_action(Args),
100
:-(
assert_contact_exists(UserJid, ContactJid),
101
:-(
{ok, _} = mod_roster_api:subscription(UserJid, ContactJid, Action),
102
:-(
{true, Req, State}.
103
104 handle_delete(Req, State = #{jid := UserJid}) ->
105
:-(
Bindings = cowboy_req:bindings(Req),
106
:-(
case try_get_jid(Bindings) of
107 undefined ->
108
:-(
Args = parse_body(Req),
109
:-(
ContactJids = get_jids_to_delete(Args),
110
:-(
NotDeleted = delete_contacts(UserJid, ContactJids),
111
:-(
RespBody = #{not_deleted => lists:map(fun jid:to_binary/1, NotDeleted)},
112
:-(
Req2 = cowboy_req:set_resp_body(jiffy:encode(RespBody), Req),
113
:-(
Req3 = cowboy_req:set_resp_header(<<"content-type">>, <<"application/json">>, Req2),
114
:-(
{true, Req3, State};
115 ContactJid ->
116
:-(
case mod_roster_api:delete_contact(UserJid, ContactJid) of
117 {contact_not_found, Reason} ->
118
:-(
throw_error(not_found, Reason);
119 {ok, _} ->
120
:-(
{true, Req, State}
121 end
122 end.
123
124 -spec roster_info(mod_roster:roster()) -> jiffy:json_value(). %% returns jiffy:json_object()
125 roster_info(Roster) ->
126
:-(
#{jid := Jid, subscription := Sub, ask := Ask} = mod_roster:item_to_map(Roster),
127
:-(
#{jid => jid:to_binary(Jid), subscription => Sub, ask => Ask}.
128
129 -spec delete_contacts(jid:jid(), [jid:jid()]) -> [jid:jid()].
130 delete_contacts(UserJid, ContactJids) ->
131
:-(
lists:filter(fun(ContactJid) ->
132
:-(
case mod_roster_api:delete_contact(UserJid, ContactJid) of
133 {contact_not_found, _Reason} ->
134
:-(
true;
135 {ok, _} ->
136
:-(
false
137 end
138 end, ContactJids).
139
140 get_jid(#{jid := JidBin}) ->
141
:-(
parse_jid(JidBin);
142 get_jid(#{}) ->
143
:-(
throw_error(bad_request, <<"Missing JID">>).
144
145 try_get_jid(#{jid := JidBin}) ->
146
:-(
parse_jid(JidBin);
147 try_get_jid(#{}) ->
148
:-(
undefined.
149
150 assert_no_jid(#{jid := _}) ->
151
:-(
throw_error(not_found, <<"JID provided but not supported">>);
152 assert_no_jid(#{}) ->
153
:-(
ok.
154
155 assert_contact_exists(UserJid, ContactJid) ->
156
:-(
case mod_roster_api:get_contact(UserJid, ContactJid) of
157
:-(
{ok, _} -> ok;
158
:-(
{_Error, Msg} -> throw_error(not_found, Msg)
159 end.
160
161 get_jids_to_delete(#{to_delete := JidsBin}) ->
162
:-(
lists:map(fun parse_jid/1, JidsBin).
163
164 parse_jid(JidBin) ->
165
:-(
case jid:from_binary(JidBin) of
166
:-(
error -> throw_error(bad_request, <<"Invalid JID: ", JidBin/binary>>);
167
:-(
Jid -> Jid
168 end.
169
170 get_action(#{action := ActionBin}) ->
171
:-(
decode_action(ActionBin);
172 get_action(#{}) ->
173
:-(
throw_error(bad_request, <<"Missing action">>).
174
175
:-(
decode_action(<<"invite">>) -> subscribe;
176
:-(
decode_action(<<"accept">>) -> subscribed;
177
:-(
decode_action(_) -> throw_error(bad_request, <<"Invalid action">>).
Line Hits Source