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 |
42 |
[{"/contacts/[:jid]", ?MODULE, #{}}]. |
27 |
|
|
28 |
|
trails() -> |
29 |
42 |
mongoose_client_api_contacts_doc:trails(). |
30 |
|
|
31 |
|
-spec init(req(), state()) -> {cowboy_rest, req(), state()}. |
32 |
|
init(Req, Opts) -> |
33 |
34 |
mongoose_client_api:init(Req, Opts). |
34 |
|
|
35 |
|
-spec is_authorized(req(), state()) -> {true | {false, iodata()}, req(), state()}. |
36 |
|
is_authorized(Req, State) -> |
37 |
34 |
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 |
32 |
{[ |
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 |
16 |
{[ |
50 |
|
{{<<"application">>, <<"json">>, '*'}, from_json} |
51 |
|
], Req, State}. |
52 |
|
|
53 |
|
-spec allowed_methods(req(), state()) -> {[binary()], req(), state()}. |
54 |
|
allowed_methods(Req, State) -> |
55 |
34 |
{[<<"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 |
12 |
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 |
16 |
F = case cowboy_req:method(Req) of |
67 |
11 |
<<"POST">> -> fun handle_post/2; |
68 |
5 |
<<"PUT">> -> fun handle_put/2 |
69 |
|
end, |
70 |
16 |
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 |
4 |
try_handle_request(Req, State, fun handle_delete/2). |
76 |
|
|
77 |
|
%% Internal functions |
78 |
|
|
79 |
|
handle_get(Req, State = #{jid := UserJid}) -> |
80 |
12 |
Bindings = cowboy_req:bindings(Req), |
81 |
12 |
assert_no_jid(Bindings), |
82 |
11 |
{ok, Contacts} = mod_roster_api:list_contacts(UserJid), |
83 |
11 |
{jiffy:encode(lists:map(fun roster_info/1, Contacts)), Req, State}. |
84 |
|
|
85 |
|
handle_post(Req, State = #{jid := UserJid}) -> |
86 |
11 |
Args = parse_body(Req), |
87 |
11 |
ContactJid = get_jid(Args), |
88 |
9 |
case mod_roster_api:add_contact(UserJid, ContactJid, <<>>, []) of |
89 |
|
{user_not_exist, Reason} -> |
90 |
1 |
throw_error(not_found, Reason); |
91 |
|
{ok, _} -> |
92 |
8 |
{true, Req, State} |
93 |
|
end. |
94 |
|
|
95 |
|
handle_put(Req, State = #{jid := UserJid}) -> |
96 |
5 |
Bindings = cowboy_req:bindings(Req), |
97 |
5 |
ContactJid = get_jid(Bindings), |
98 |
5 |
Args = parse_body(Req), |
99 |
5 |
Action = get_action(Args), |
100 |
3 |
assert_contact_exists(UserJid, ContactJid), |
101 |
2 |
{ok, _} = mod_roster_api:subscription(UserJid, ContactJid, Action), |
102 |
2 |
{true, Req, State}. |
103 |
|
|
104 |
|
handle_delete(Req, State = #{jid := UserJid}) -> |
105 |
4 |
Bindings = cowboy_req:bindings(Req), |
106 |
4 |
case try_get_jid(Bindings) of |
107 |
|
undefined -> |
108 |
2 |
Args = parse_body(Req), |
109 |
2 |
ContactJids = get_jids_to_delete(Args), |
110 |
2 |
NotDeleted = delete_contacts(UserJid, ContactJids), |
111 |
2 |
RespBody = #{not_deleted => lists:map(fun jid:to_binary/1, NotDeleted)}, |
112 |
2 |
Req2 = cowboy_req:set_resp_body(jiffy:encode(RespBody), Req), |
113 |
2 |
Req3 = cowboy_req:set_resp_header(<<"content-type">>, <<"application/json">>, Req2), |
114 |
2 |
{true, Req3, State}; |
115 |
|
ContactJid -> |
116 |
2 |
case mod_roster_api:delete_contact(UserJid, ContactJid) of |
117 |
|
{contact_not_found, Reason} -> |
118 |
1 |
throw_error(not_found, Reason); |
119 |
|
{ok, _} -> |
120 |
1 |
{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 |
7 |
#{jid := Jid, subscription := Sub, ask := Ask} = mod_roster:item_to_map(Roster), |
127 |
7 |
#{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 |
2 |
lists:filter(fun(ContactJid) -> |
132 |
5 |
case mod_roster_api:delete_contact(UserJid, ContactJid) of |
133 |
|
{contact_not_found, _Reason} -> |
134 |
1 |
true; |
135 |
|
{ok, _} -> |
136 |
4 |
false |
137 |
|
end |
138 |
|
end, ContactJids). |
139 |
|
|
140 |
|
get_jid(#{jid := JidBin}) -> |
141 |
15 |
parse_jid(JidBin); |
142 |
|
get_jid(#{}) -> |
143 |
1 |
throw_error(bad_request, <<"Missing JID">>). |
144 |
|
|
145 |
|
try_get_jid(#{jid := JidBin}) -> |
146 |
2 |
parse_jid(JidBin); |
147 |
|
try_get_jid(#{}) -> |
148 |
2 |
undefined. |
149 |
|
|
150 |
|
assert_no_jid(#{jid := _}) -> |
151 |
1 |
throw_error(not_found, <<"JID provided but not supported">>); |
152 |
|
assert_no_jid(#{}) -> |
153 |
11 |
ok. |
154 |
|
|
155 |
|
assert_contact_exists(UserJid, ContactJid) -> |
156 |
3 |
case mod_roster_api:get_contact(UserJid, ContactJid) of |
157 |
2 |
{ok, _} -> ok; |
158 |
1 |
{_Error, Msg} -> throw_error(not_found, Msg) |
159 |
|
end. |
160 |
|
|
161 |
|
get_jids_to_delete(#{to_delete := JidsBin}) -> |
162 |
2 |
lists:map(fun parse_jid/1, JidsBin). |
163 |
|
|
164 |
|
parse_jid(JidBin) -> |
165 |
22 |
case jid:from_binary(JidBin) of |
166 |
1 |
error -> throw_error(bad_request, <<"Invalid JID: ", JidBin/binary>>); |
167 |
21 |
Jid -> Jid |
168 |
|
end. |
169 |
|
|
170 |
|
get_action(#{action := ActionBin}) -> |
171 |
4 |
decode_action(ActionBin); |
172 |
|
get_action(#{}) -> |
173 |
1 |
throw_error(bad_request, <<"Missing action">>). |
174 |
|
|
175 |
2 |
decode_action(<<"invite">>) -> subscribe; |
176 |
1 |
decode_action(<<"accept">>) -> subscribed; |
177 |
1 |
decode_action(_) -> throw_error(bad_request, <<"Invalid action">>). |