./ct_report/coverage/mod_inbox_entries.COVER.html

1 -module(mod_inbox_entries).
2
3 -include("mongoose_ns.hrl").
4 -include("jlib.hrl").
5 -include("mod_inbox.hrl").
6
7 % Inbox extensions
8 -export([process_iq_conversation/5]).
9 -export([should_be_stored_in_inbox/1]).
10 -export([extensions_result/2]).
11
12 -type entry_query() :: #{box => binary(),
13 unread_count => 0 | 1,
14 muted_until => integer()}.
15
16 -type get_entry_type() :: full_entry | only_properties.
17 -export_type([get_entry_type/0]).
18
19 -spec process_iq_conversation(Acc :: mongoose_acc:t(),
20 From :: jid:jid(),
21 To :: jid:jid(),
22 IQ :: jlib:iq(),
23 Extra :: map()) ->
24 {mongoose_acc:t(), jlib:iq()}.
25 process_iq_conversation(Acc, From, _To, #iq{type = get, sub_el = SubEl} = IQ, _Extra) ->
26 6 process_iq_conversation_get(Acc, IQ, From, SubEl);
27 process_iq_conversation(Acc, From, _To, #iq{type = set,
28 sub_el = #xmlel{name = <<"reset">>} = ResetStanza} = IQ,
29 _Extra) ->
30 10 maybe_process_reset_stanza(Acc, From, IQ, ResetStanza);
31 process_iq_conversation(Acc, From, _To, #iq{type = set,
32 sub_el = #xmlel{name = <<"query">>} = Query} = IQ,
33 _Extra) ->
34 118 process_iq_conversation_set(Acc, IQ, From, Query).
35
36 -spec process_iq_conversation_get(mongoose_acc:t(), jlib:iq(), jid:jid(), exml:element()) ->
37 {mongoose_acc:t(), jlib:iq()}.
38 process_iq_conversation_get(Acc, IQ, From, SubEl) ->
39 6 case mod_inbox_utils:extract_attr_jid(SubEl) of
40 {error, _} ->
41 2 HostType = mongoose_acc:host_type(Acc),
42 2 Form = build_inbox_entry_form(HostType),
43 2 SubElWithForm = SubEl#xmlel{children = [Form]},
44 2 {Acc, IQ#iq{type = result, sub_el = SubElWithForm}};
45 EntryJID ->
46 4 FullEntry = maybe_get_full_entry(SubEl),
47 4 get_properties_for_jid(Acc, IQ, From, EntryJID, FullEntry)
48 end.
49
50 -spec maybe_get_full_entry(exml:element()) -> get_entry_type().
51 maybe_get_full_entry(SubEl) ->
52 4 case exml_query:attr(SubEl, <<"complete">>) of
53 2 <<"true">> -> full_entry;
54 2 _ -> only_properties
55 end.
56
57 -spec build_inbox_entry_form(mongooseim:host_type()) -> exml:element().
58 build_inbox_entry_form(HostType) ->
59 2 AllBoxes = mod_inbox_utils:all_valid_boxes_for_query(HostType),
60 2 #xmlel{name = <<"x">>,
61 attrs = [{<<"xmlns">>, ?NS_XDATA},
62 {<<"type">>, <<"form">>}],
63 children = [jlib:form_field({<<"FORM_TYPE">>, <<"hidden">>, ?NS_ESL_INBOX_CONVERSATION}),
64 mod_inbox_utils:list_single_form_field(<<"box">>, <<"all">>, AllBoxes),
65 jlib:form_field({<<"archive">>, <<"boolean">>, <<"false">>}),
66 jlib:form_field({<<"read">>, <<"boolean">>, <<"false">>}),
67 jlib:form_field({<<"mute">>, <<"text-single">>, <<"0">>})]}.
68
69 fetch_right_query(HostType, InboxEntryKey, only_properties) ->
70 2 mod_inbox_backend:get_entry_properties(HostType, InboxEntryKey);
71 fetch_right_query(HostType, InboxEntryKey, full_entry) ->
72 2 mod_inbox_backend:get_full_entry(HostType, InboxEntryKey).
73
74 -spec get_properties_for_jid(mongoose_acc:t(), jlib:iq(), jid:jid(), jid:jid(), get_entry_type()) ->
75 {mongoose_acc:t(), jlib:iq()}.
76 get_properties_for_jid(Acc, IQ, From, EntryJID, QueryType) ->
77 4 HostType = mongoose_acc:host_type(Acc),
78 4 {_, _, BinEntryJID} = InboxEntryKey = mod_inbox_utils:build_inbox_entry_key(From, EntryJID),
79 4 case fetch_right_query(HostType, InboxEntryKey, QueryType) of
80
:-(
[] -> return_error(Acc, IQ, item_not_found, <<"Entry not found">>);
81 Result ->
82 4 Properties = build_result(Acc, Result, IQ, QueryType),
83 4 X = [#xmlel{name = <<"query">>,
84 attrs = [{<<"xmlns">>, ?NS_ESL_INBOX_CONVERSATION},
85 {<<"jid">>, BinEntryJID}],
86 children = Properties}],
87 4 {Acc, IQ#iq{type = result, sub_el = X}}
88 end.
89
90 -spec process_iq_conversation_set(mongoose_acc:t(), jlib:iq(), jid:jid(), exml:element()) ->
91 {mongoose_acc:t(), jlib:iq()}.
92 process_iq_conversation_set(Acc, IQ, From, #xmlel{children = Requests} = Query) ->
93 118 case mod_inbox_utils:extract_attr_jid(Query) of
94 {error, Msg} ->
95 4 return_error(Acc, IQ, bad_request, Msg);
96 EntryJID ->
97 114 extract_requests(Acc, IQ, From, EntryJID, Requests)
98 end.
99
100 -spec extract_requests(mongoose_acc:t(), jlib:iq(), jid:jid(), jid:jid(), [exml:element()]) ->
101 {mongoose_acc:t(), jlib:iq()}.
102 extract_requests(Acc, IQ, From, EntryJID, Requests) ->
103 114 case form_to_query(Acc, Requests, #{}) of
104 {error, Msg} ->
105 12 return_error(Acc, IQ, bad_request, Msg);
106 Params ->
107 102 process_requests(Acc, IQ, From, EntryJID, Params)
108 end.
109
110 -spec process_requests(mongoose_acc:t(), jlib:iq(), jid:jid(), jid:jid(), entry_query()) ->
111 {mongoose_acc:t(), jlib:iq()}.
112 process_requests(Acc, IQ, From, EntryJID, Params) ->
113 102 HostType = mongoose_acc:host_type(Acc),
114 102 InboxEntryKey = mod_inbox_utils:build_inbox_entry_key(From, EntryJID),
115 102 case mod_inbox_backend:set_entry_properties(HostType, InboxEntryKey, Params) of
116 {error, Msg} ->
117 8 return_error(Acc, IQ, bad_request, Msg);
118 Result ->
119 94 forward_result(Acc, IQ, From, InboxEntryKey, Result)
120 end.
121
122 -spec forward_result(mongoose_acc:t(), jlib:iq(), jid:jid(), mod_inbox:entry_key(), entry_properties()) ->
123 {mongoose_acc:t(), jlib:iq()}.
124 forward_result(Acc, IQ = #iq{id = IqId}, From, {_, _, ToBareJidBin}, Result) ->
125 94 Properties = build_result(Acc, Result, IQ, only_properties),
126 94 Children = iq_x_wrapper(IQ, ToBareJidBin, Properties),
127 94 Msg = #xmlel{name = <<"message">>, attrs = [{<<"id">>, IqId}], children = Children},
128 94 Acc1 = ejabberd_router:route(From, jid:to_bare(From), Acc, Msg),
129 94 Res = IQ#iq{type = result, sub_el = []},
130 94 {Acc1, Res}.
131
132 -spec iq_x_wrapper(jlib:iq(), binary(), [exml:element()]) -> [exml:element()].
133 iq_x_wrapper(#iq{id = IqId, sub_el = Query}, Jid, Properties) ->
134 94 [#xmlel{name = <<"x">>,
135 attrs = [{<<"xmlns">>, ?NS_ESL_INBOX_CONVERSATION},
136 {<<"jid">>, Jid},
137 {<<"queryid">>, exml_query:attr(Query, <<"queryid">>, IqId)}],
138 children = Properties}].
139
140 maybe_process_reset_stanza(Acc, From, IQ, ResetStanza) ->
141 10 case mod_inbox_utils:extract_attr_jid(ResetStanza) of
142 {error, Msg} ->
143 4 return_error(Acc, IQ, bad_request, Msg);
144 InterlocutorJID ->
145 6 process_reset_stanza(Acc, From, IQ, ResetStanza, InterlocutorJID)
146 end.
147
148 process_reset_stanza(Acc, From, IQ, _ResetStanza, InterlocutorJID) ->
149 6 ok = mod_inbox_utils:reset_unread_count_to_zero(Acc, From, InterlocutorJID),
150 6 Res = IQ#iq{type = result,
151 sub_el = [#xmlel{name = <<"reset">>,
152 attrs = [{<<"xmlns">>, ?NS_ESL_INBOX_CONVERSATION}],
153 children = []}]},
154 6 {Acc, Res}.
155
156 %%--------------------------------------------------------------------
157 %% Helpers
158 %%--------------------------------------------------------------------
159
160 -spec build_result(mongoose_acc:t(), inbox_res() | entry_properties(), jlib:iq(), get_entry_type()) ->
161 [exml:element()].
162 build_result(Acc, Entry = #{unread_count := UnreadCount}, IQ, QueryType) ->
163 98 CurrentTS = mongoose_acc:timestamp(Acc),
164 98 maybe_full_entry(Acc, Entry, IQ, QueryType) ++
165 [ kv_to_el(<<"read">>, mod_inbox_utils:bool_to_binary(0 =:= UnreadCount))
166 | extensions_result(Entry, CurrentTS)].
167
168 maybe_full_entry(Acc, Entry, IQ, full_entry) ->
169 2 Extensions = mongoose_hooks:extend_inbox_message(Acc, Entry, IQ),
170 2 [ mod_inbox_utils:build_forward_el(Entry) | Extensions];
171 maybe_full_entry(_, _, _, _) ->
172 96 [].
173
174 -spec return_error(mongoose_acc:t(), jlib:iq(), bad_request | item_not_found, binary()) ->
175 {mongoose_acc:t(), jlib:iq()}.
176 return_error(Acc, IQ, Type, Msg) when is_binary(Msg) ->
177 28 {Acc, IQ#iq{type = error, sub_el = [mongoose_xmpp_errors:Type(<<"en">>, Msg)]}}.
178
179 -spec form_to_query(mongoose_acc:t(), [exml:element()], map()) -> entry_query() | {error, binary()}.
180 form_to_query(_, [], Acc) when map_size(Acc) == 0 ->
181 2 {error, <<"no-property">>};
182 form_to_query(_, [], Acc) ->
183 102 Acc;
184 form_to_query(MAcc, [#xmlel{name = <<"box">>, children = [#xmlcdata{content = BoxName}]} | Rest], Acc) ->
185 40 case is_box_accepted(MAcc, BoxName) of
186 38 true -> form_to_query(MAcc, Rest, Acc#{box => BoxName});
187 2 false -> {error, <<"invalid-box">>}
188 end;
189 form_to_query(MAcc, [#xmlel{name = <<"archive">>, children = [#xmlcdata{content = <<"true">>}]} | Rest], Acc) ->
190 28 form_to_query(MAcc, Rest, Acc#{box => maps:get(box, Acc, <<"archive">>)});
191 form_to_query(MAcc, [#xmlel{name = <<"archive">>, children = [#xmlcdata{content = <<"false">>}]} | Rest], Acc) ->
192 2 form_to_query(MAcc, Rest, Acc#{box => maps:get(box, Acc, <<"inbox">>)});
193 form_to_query(MAcc, [#xmlel{name = <<"read">>, children = [#xmlcdata{content = <<"true">>}]} | Rest], Acc) ->
194 16 form_to_query(MAcc, Rest, Acc#{unread_count => 0});
195 form_to_query(MAcc, [#xmlel{name = <<"read">>, children = [#xmlcdata{content = <<"false">>}]} | Rest], Acc) ->
196 8 form_to_query(MAcc, Rest, Acc#{unread_count => 1});
197 form_to_query(MAcc, [#xmlel{name = <<"mute">>,
198 children = [#xmlcdata{content = Value}]} | Rest], Acc) ->
199 24 case mod_inbox_utils:maybe_binary_to_positive_integer(Value) of
200 4 {error, _} -> {error, <<"bad-request">>};
201 0 ->
202 2 form_to_query(MAcc, Rest, Acc#{muted_until => 0});
203 Num when Num > 0 ->
204 18 TS = mongoose_acc:timestamp(MAcc),
205 18 MutedUntilMicroSec = TS + erlang:convert_time_unit(Num, second, microsecond),
206 18 form_to_query(MAcc, Rest, Acc#{muted_until => MutedUntilMicroSec})
207 end;
208 form_to_query(_, _, _) ->
209 4 {error, <<"bad-request">>}.
210
211 is_box_accepted(MAcc, Box) ->
212 40 HostType = mongoose_acc:host_type(MAcc),
213 40 BoxOptions = gen_mod:get_module_opt(HostType, mod_inbox, boxes),
214 40 lists:member(Box, BoxOptions).
215
216 -spec should_be_stored_in_inbox(exml:element()) -> boolean().
217 should_be_stored_in_inbox(Msg) ->
218 1448 not is_inbox_update(Msg).
219
220 -spec is_inbox_update(exml:element()) -> boolean().
221 is_inbox_update(Msg) ->
222 1448 case exml_query:subelement_with_ns(Msg, ?NS_ESL_INBOX_CONVERSATION, undefined) of
223 1448 undefined -> false;
224
:-(
_ -> true
225 end.
226
227 -spec extensions_result(entry_properties(), integer()) -> [exml:element()].
228 extensions_result(#{box := Box, muted_until := MutedUntil}, AccTS) ->
229 779 [ kv_to_el(<<"box">>, Box),
230 kv_to_el(<<"archive">>, is_archive(Box)),
231 kv_to_el(<<"mute">>, maybe_muted_until(MutedUntil, AccTS))].
232
233 -spec kv_to_el(binary(), binary()) -> exml:element().
234 kv_to_el(Key, Value) ->
235 2435 #xmlel{name = Key, children = [#xmlcdata{content = Value}]}.
236
237 88 is_archive(<<"archive">>) -> <<"true">>;
238 691 is_archive(_) -> <<"false">>.
239
240 -spec maybe_muted_until(integer(), integer()) -> binary().
241 741 maybe_muted_until(0, _) -> <<"0">>;
242 maybe_muted_until(MutedUntil, CurrentTS) ->
243 38 case CurrentTS =< MutedUntil of
244 36 true -> list_to_binary(calendar:system_time_to_rfc3339(MutedUntil, [{offset, "Z"}, {unit, microsecond}]));
245 2 false -> <<"0">>
246 end.
Line Hits Source