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