./ct_report/coverage/mongoose_admin_api_messages.COVER.html

1 -module(mongoose_admin_api_messages).
2
3 -behaviour(mongoose_admin_api).
4 -export([routes/1]).
5
6 -behaviour(cowboy_rest).
7 -export([init/2,
8 is_authorized/2,
9 content_types_provided/2,
10 content_types_accepted/2,
11 allowed_methods/2,
12 to_json/2,
13 from_json/2]).
14
15 -ignore_xref([to_json/2, from_json/2]).
16
17 -import(mongoose_admin_api, [parse_body/1, parse_qs/1, try_handle_request/3, throw_error/2]).
18
19 -type req() :: cowboy_req:req().
20 -type state() :: mongoose_admin_api:state().
21
22 -spec routes(state()) -> mongoose_http_handler:routes().
23 routes(State) ->
24 110 [{"/messages/:owner/:with", ?MODULE, State},
25 {"/messages/[:owner]", ?MODULE, State}].
26
27 -spec init(req(), state()) -> {cowboy_rest, req(), state()}.
28 init(Req, State) ->
29 25 mongoose_admin_api:init(Req, State).
30
31 -spec is_authorized(req(), state()) -> {true | {false, iodata()}, req(), state()}.
32 is_authorized(Req, State) ->
33 25 mongoose_admin_api:is_authorized(Req, State).
34
35 -spec content_types_provided(req(), state()) ->
36 {[{{binary(), binary(), '*'}, atom()}], req(), state()}.
37 content_types_provided(Req, State) ->
38 25 {[
39 {{<<"application">>, <<"json">>, '*'}, to_json}
40 ], Req, State}.
41
42 -spec content_types_accepted(req(), state()) ->
43 {[{{binary(), binary(), '*'}, atom()}], req(), state()}.
44 content_types_accepted(Req, State) ->
45 11 {[
46 {{<<"application">>, <<"json">>, '*'}, from_json}
47 ], Req, State}.
48
49 -spec allowed_methods(req(), state()) -> {[binary()], req(), state()}.
50 allowed_methods(Req, State) ->
51 25 {[<<"OPTIONS">>, <<"GET">>, <<"POST">>], Req, State}.
52
53 %% @doc Called for a method of type "GET"
54 -spec to_json(req(), state()) -> {iodata() | stop, req(), state()}.
55 to_json(Req, State) ->
56 14 try_handle_request(Req, State, fun handle_get/2).
57
58 %% @doc Called for a method of type "POST"
59 -spec from_json(req(), state()) -> {true | stop, req(), state()}.
60 from_json(Req, State) ->
61 11 try_handle_request(Req, State, fun handle_post/2).
62
63 %% Internal functions
64
65 handle_get(Req, State) ->
66 14 Bindings = cowboy_req:bindings(Req),
67 14 OwnerJid = get_owner_jid(Bindings),
68 12 WithJid = get_with_jid(Bindings),
69 11 Args = parse_qs(Req),
70 10 Limit = get_limit(Args),
71 9 Before = get_before(Args),
72 8 case mongoose_stanza_api:lookup_recent_messages(OwnerJid, WithJid, Before, Limit, true) of
73 {ok, {Rows, _Limit}} ->
74 7 Messages = lists:map(fun row_to_map/1, Rows),
75 7 {jiffy:encode(Messages), Req, State};
76 {unknown_user, Msg} ->
77 1 throw_error(bad_request, Msg)
78 end.
79
80 handle_post(Req, State) ->
81 11 Args = parse_body(Req),
82 11 From = get_caller(Args),
83 9 To = get_to(Args),
84 7 Body = get_body(Args),
85 6 case mongoose_stanza_api:send_chat_message(undefined, From, To, Body) of
86 {ok, _} ->
87 4 {true, Req, State};
88 {_Error, Msg} ->
89 2 throw_error(bad_request, Msg)
90 end.
91
92 -spec row_to_map(mod_mam:message_row()) -> map().
93 row_to_map(#{id := Id, jid := From, packet := Msg}) ->
94 21 Jbin = jid:to_binary(From),
95 21 {Msec, _} = mod_mam_utils:decode_compact_uuid(Id),
96 21 MsgId = case xml:get_tag_attr(<<"id">>, Msg) of
97 6 {value, MId} -> MId;
98 15 false -> <<"">>
99 end,
100 21 Body = exml_query:path(Msg, [{element, <<"body">>}, cdata]),
101 21 #{sender => Jbin, timestamp => round(Msec / 1000000), message_id => MsgId, body => Body}.
102
103 get_limit(#{limit := LimitBin}) ->
104 6 try
105 6 Limit = binary_to_integer(LimitBin),
106 5 true = Limit >= 0,
107 5 Limit
108 catch
109 1 _:_ -> throw_error(bad_request, <<"Invalid limit">>)
110 end;
111 4 get_limit(#{}) -> 100.
112
113 get_before(#{before := BeforeBin}) ->
114 3 try
115 3 1000000 * binary_to_integer(BeforeBin)
116 catch
117 1 _:_ -> throw_error(bad_request, <<"Invalid value of 'before'">>)
118 end;
119 6 get_before(#{}) -> undefined.
120
121 get_owner_jid(#{owner := Owner}) ->
122 13 case jid:from_binary(Owner) of
123 1 error -> throw_error(bad_request, <<"Invalid owner JID">>);
124 12 OwnerJid -> OwnerJid
125 end;
126 1 get_owner_jid(#{}) -> throw_error(not_found, <<"Missing owner JID">>).
127
128 get_with_jid(#{with := With}) ->
129 8 case jid:from_binary(With) of
130 1 error -> throw_error(bad_request, <<"Invalid interlocutor JID">>);
131 7 WithJid -> WithJid
132 end;
133 4 get_with_jid(#{}) -> undefined.
134
135 get_caller(#{caller := Caller}) ->
136 10 case jid:from_binary(Caller) of
137 1 error -> throw_error(bad_request, <<"Invalid sender JID">>);
138 9 CallerJid -> CallerJid
139 end;
140 1 get_caller(#{}) -> throw_error(bad_request, <<"Missing sender JID">>).
141
142 get_to(#{to := To}) ->
143 8 case jid:from_binary(To) of
144 1 error -> throw_error(bad_request, <<"Invalid recipient JID">>);
145 7 ToJid -> ToJid
146 end;
147 1 get_to(#{}) -> throw_error(bad_request, <<"Missing recipient JID">>).
148
149 6 get_body(#{body := Body}) -> Body;
150 1 get_body(#{}) -> throw_error(bad_request, <<"Missing message body">>).
Line Hits Source