./ct_report/coverage/mongoose_client_api_rooms_messages.COVER.html

1 -module(mongoose_client_api_rooms_messages).
2 -behaviour(cowboy_rest).
3
4 -export([trails/0]).
5
6 -export([init/2]).
7 -export([content_types_provided/2]).
8 -export([content_types_accepted/2]).
9 -export([is_authorized/2]).
10 -export([allowed_methods/2]).
11 -export([resource_exists/2]).
12 -export([allow_missing_post/2]).
13
14 -export([to_json/2]).
15 -export([from_json/2]).
16 -export([encode/2]).
17
18 -ignore_xref([from_json/2, to_json/2, trails/0]).
19
20 -import(mongoose_client_api_messages, [maybe_integer/1, maybe_before_to_us/2]).
21
22 -include("mongoose.hrl").
23 -include("jlib.hrl").
24 -include("mongoose_rsm.hrl").
25 -include_lib("exml/include/exml.hrl").
26
27 trails() ->
28 80 mongoose_client_api_rooms_messages_doc:trails().
29
30 init(Req, Opts) ->
31 8 mongoose_client_api:init(Req, Opts).
32
33 is_authorized(Req, State) ->
34 8 mongoose_client_api:is_authorized(Req, State).
35
36 content_types_provided(Req, State) ->
37 8 mongoose_client_api_rooms:content_types_provided(Req, State).
38
39 content_types_accepted(Req, State) ->
40 8 mongoose_client_api_rooms:content_types_accepted(Req, State).
41
42 allowed_methods(Req, State) ->
43 8 {[<<"OPTIONS">>, <<"GET">>, <<"POST">>], Req, State}.
44
45 resource_exists(Req, State) ->
46 8 mongoose_client_api_rooms:resource_exists(Req, State).
47
48 allow_missing_post(Req, State) ->
49
:-(
{false, Req, State}.
50
51 to_json(Req, #{role_in_room := none} = State) ->
52
:-(
mongoose_client_api:forbidden_request(Req, State);
53 to_json(Req, #{jid := UserJID, room := Room} = State) ->
54
:-(
RoomJID = maps:get(jid, Room),
55
:-(
HostType = mod_muc_light_utils:room_jid_to_host_type(RoomJID),
56
:-(
Now = os:system_time(microsecond),
57
:-(
ArchiveID = mod_mam_muc:archive_id_int(HostType, RoomJID),
58
:-(
QS = cowboy_req:parse_qs(Req),
59
:-(
PageSize = maybe_integer(proplists:get_value(<<"limit">>, QS, <<"50">>)),
60
:-(
Before = maybe_integer(proplists:get_value(<<"before">>, QS)),
61
:-(
End = maybe_before_to_us(Before, Now),
62
:-(
RSM = #rsm_in{direction = before, id = undefined},
63
:-(
R = mod_mam_muc:lookup_messages(HostType,
64 #{archive_id => ArchiveID,
65 owner_jid => RoomJID,
66 caller_jid => UserJID,
67 rsm => RSM,
68 borders => undefined,
69 start_ts => undefined,
70 end_ts => End,
71 now => Now,
72 with_jid => undefined,
73 search_text => undefined,
74 page_size => PageSize,
75 limit_passed => true,
76 max_result_limit => 50,
77 is_simple => true}),
78
:-(
{ok, {_, _, Msgs}} = R,
79
:-(
JSONData = [make_json_item(Msg) || Msg <- Msgs],
80
:-(
{jiffy:encode(JSONData), Req, State}.
81
82 from_json(Req, #{role_in_room := none} = State) ->
83 1 mongoose_client_api:forbidden_request(Req, State);
84 from_json(Req, #{user := User, jid := JID, room := Room} = State) ->
85 7 {ok, Body, Req2} = cowboy_req:read_body(Req),
86 7 case mongoose_client_api:json_to_map(Body) of
87 {ok, JSONData} ->
88 6 prepare_message_and_route_to_room(User, JID, Room, State, Req2, JSONData);
89 _ ->
90 1 mongoose_client_api:bad_request(Req2, <<"Request body is not a valid JSON">>, State)
91 end.
92
93
94 prepare_message_and_route_to_room(User, JID, Room, State, Req, JSONData) ->
95 6 RoomJID = #jid{lserver = _} = maps:get(jid, Room),
96 6 UUID = uuid:uuid_to_string(uuid:get_v4(), binary_standard),
97 6 {ok, HostType} = mongoose_domain_api:get_domain_host_type(JID#jid.lserver),
98 6 case build_message_from_json(User, RoomJID, UUID, JSONData) of
99 {ok, Message} ->
100 3 Acc = mongoose_acc:new(#{ location => ?LOCATION,
101 host_type => HostType,
102 lserver => JID#jid.lserver,
103 from_jid => JID,
104 to_jid => RoomJID,
105 element => Message }),
106 3 ejabberd_router:route(JID, RoomJID, Acc, Message),
107 3 Resp = #{id => UUID},
108 3 Req3 = cowboy_req:set_resp_body(jiffy:encode(Resp), Req),
109 3 {true, Req3, State};
110 {error, ErrorMsg} ->
111 3 Req2 = cowboy_req:set_resp_body(ErrorMsg, Req),
112 3 mongoose_client_api:bad_request(Req2, State)
113 end.
114
115 -spec build_message_from_json(From :: binary(), To :: jid:jid(), ID :: binary(), JSON :: map()) ->
116 {ok, exml:element()} | {error, ErrorMsg :: binary()}.
117 build_message_from_json(From, To, ID, JSON) ->
118 6 case build_children(JSON) of
119 {error, _} = Err ->
120 1 Err;
121 [] ->
122 2 {error, <<"No valid message elements">>};
123 Children ->
124 3 Attrs = [{<<"from">>, From},
125 {<<"to">>, jid:to_binary(To)},
126 {<<"id">>, ID},
127 {<<"type">>, <<"groupchat">>}],
128 3 {ok, #xmlel{name = <<"message">>, attrs = Attrs, children = Children}}
129 end.
130
131 build_children(JSON) ->
132 6 lists:foldl(fun(_, {error, _} = Err) ->
133
:-(
Err;
134 (ChildBuilder, Children) ->
135 18 ChildBuilder(JSON, Children)
136 end, [], [fun build_markable/2, fun build_marker/2, fun build_body/2]).
137
138 build_body(#{ <<"body">> := Body }, Children) when is_binary(Body) ->
139 3 [#xmlel{ name = <<"body">>, children = [#xmlcdata{ content = Body }] } | Children];
140 build_body(#{ <<"body">> := _Body }, _Children) ->
141 1 {error, <<"Invalid body, it must be a string">>};
142 build_body(_JSON, Children) ->
143 2 Children.
144
145 build_marker(#{ <<"chat_marker">> := #{ <<"type">> := Type, <<"id">> := Id } }, Children)
146 when Type == <<"received">>;
147 Type == <<"displayed">>;
148 Type == <<"acknowledged">> ->
149
:-(
[#xmlel{ name = Type, attrs = [{<<"xmlns">>, ?NS_CHAT_MARKERS}, {<<"id">>, Id}] } | Children];
150 build_marker(#{ <<"chat_marker">> := _Marker }, _Children) ->
151
:-(
{error, <<"Invalid marker, it must be 'received', 'displayed' or 'acknowledged'">>};
152 build_marker(_JSON, Children) ->
153 6 Children.
154
155 build_markable(#{ <<"body">> := _Body, <<"markable">> := true }, Children) ->
156
:-(
[#xmlel{ name = <<"markable">>, attrs = [{<<"xmlns">>, ?NS_CHAT_MARKERS}] } | Children];
157 build_markable(_JSON, Children) ->
158 6 Children.
159
160 -spec encode(Packet :: exml:element(), Timestamp :: integer()) -> map().
161 encode(Packet, Timestamp) ->
162 2 From = exml_query:attr(Packet, <<"from">>),
163 2 FromJID = jid:from_binary(From),
164 2 Msg = make_json_item(Packet, FromJID, Timestamp),
165 2 Msg#{room => FromJID#jid.luser}.
166
167 -spec make_json_item(mod_mam:message_row()) -> term().
168 make_json_item(#{id := MAMID, jid := JID, packet := Msg}) ->
169
:-(
{Microsec, _} = mod_mam_utils:decode_compact_uuid(MAMID),
170
:-(
make_json_item(Msg, JID, Microsec div 1000).
171
172 make_json_item(Msg, JID, Timestamp) ->
173 2 Item = #{id => exml_query:attr(Msg, <<"id">>),
174 from => make_from(JID),
175 timestamp => Timestamp},
176 2 add_body_and_type(Item, Msg).
177
178 make_from(#jid{lresource = <<>>} = JID) ->
179 1 jid:to_binary(JID);
180 make_from(#jid{lresource = Sender}) ->
181 1 Sender.
182
183 add_body_and_type(Item, Msg) ->
184 2 case exml_query:path(Msg, [{element, <<"x">>}, {element, <<"user">>}]) of
185 undefined ->
186 1 add_regular_message_body(Item, Msg);
187 #xmlel{} = AffChange ->
188 1 add_aff_change_body(Item, AffChange)
189 end.
190
191 add_regular_message_body(Item0, Msg) ->
192 1 Item1 = Item0#{type => <<"message">>},
193 1 Item2 =
194 case exml_query:path(Msg, [{element, <<"body">>}, cdata]) of
195 undefined ->
196
:-(
Item1;
197 Body ->
198 1 Item1#{body => Body}
199 end,
200 1 add_chat_marker(Item2, Msg).
201
202 add_chat_marker(Item0, Msg) ->
203 1 case exml_query:subelement_with_ns(Msg, ?NS_CHAT_MARKERS) of
204 undefined ->
205 1 Item0;
206 #xmlel{ name = <<"markable">> } ->
207
:-(
Item0#{ markable => true };
208 #xmlel{ name = Type } = Marker ->
209
:-(
Item0#{ chat_marker => #{ type => Type, id => exml_query:attr(Marker, <<"id">>) } }
210 end.
211
212 add_aff_change_body(Item, #xmlel{attrs = Attrs} = User) ->
213 1 Item#{type => <<"affiliation">>,
214 affiliation => proplists:get_value(<<"affiliation">>, Attrs),
215 user => exml_query:cdata(User)}.
Line Hits Source