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