./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
:-(
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
:-(
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
:-(
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
:-(
case mod_inbox_utils:extract_attr_jid(SubEl) of
37 {error, _} ->
38
:-(
HostType = mongoose_acc:host_type(Acc),
39
:-(
Form = build_inbox_entry_form(HostType),
40
:-(
SubElWithForm = SubEl#xmlel{children = [Form]},
41
:-(
{Acc, IQ#iq{type = result, sub_el = SubElWithForm}};
42 EntryJID ->
43
:-(
FullEntry = maybe_get_full_entry(SubEl),
44
:-(
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
:-(
case exml_query:attr(SubEl, <<"complete">>) of
50
:-(
<<"true">> -> full_entry;
51
:-(
_ -> only_properties
52 end.
53
54 -spec build_inbox_entry_form(mongooseim:host_type()) -> exml:element().
55 build_inbox_entry_form(HostType) ->
56
:-(
AllBoxes = mod_inbox_utils:all_valid_boxes_for_query(HostType),
57
:-(
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
:-(
mongoose_data_forms:form(#{ns => ?NS_ESL_INBOX_CONVERSATION, fields => Fields}).
63
64 fetch_right_query(HostType, InboxEntryKey, only_properties) ->
65
:-(
mod_inbox_backend:get_entry_properties(HostType, InboxEntryKey);
66 fetch_right_query(HostType, InboxEntryKey, full_entry) ->
67
:-(
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
:-(
HostType = mongoose_acc:host_type(Acc),
73
:-(
{_, _, BinEntryJID} = InboxEntryKey = mod_inbox_utils:build_inbox_entry_key(From, EntryJID),
74
:-(
case fetch_right_query(HostType, InboxEntryKey, QueryType) of
75
:-(
[] -> return_error(Acc, IQ, item_not_found, <<"Entry not found">>);
76 Result ->
77
:-(
Children = build_query_children(Acc, Result, IQ, QueryType),
78
:-(
X = [#xmlel{name = <<"query">>,
79 attrs = [{<<"xmlns">>, ?NS_ESL_INBOX_CONVERSATION},
80 {<<"jid">>, BinEntryJID}],
81 children = Children}],
82
:-(
{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
:-(
case mod_inbox_utils:extract_attr_jid(Query) of
89 {error, Msg} ->
90
:-(
return_error(Acc, IQ, bad_request, Msg);
91 EntryJID ->
92
:-(
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
:-(
case form_to_query(Acc, Requests, #{}) of
99 {error, Msg} ->
100
:-(
return_error(Acc, IQ, bad_request, Msg);
101 Params ->
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
:-(
HostType = mongoose_acc:host_type(Acc),
109
:-(
InboxEntryKey = mod_inbox_utils:build_inbox_entry_key(From, EntryJID),
110
:-(
case mod_inbox_backend:set_entry_properties(HostType, InboxEntryKey, Params) of
111 {error, Msg} ->
112
:-(
return_error(Acc, IQ, bad_request, Msg);
113 Result ->
114
:-(
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
:-(
Children0 = build_query_children(Acc, Result, IQ, only_properties),
121
:-(
Children1 = iq_x_wrapper(IQ, ToBareJidBin, Children0),
122
:-(
Msg = #xmlel{name = <<"message">>, attrs = [{<<"id">>, IqId}], children = Children1},
123
:-(
Acc1 = ejabberd_router:route(From, jid:to_bare(From), Acc, Msg),
124
:-(
Res = IQ#iq{type = result, sub_el = []},
125
:-(
{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
:-(
[#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
:-(
case mod_inbox_utils:extract_attr_jid(ResetStanza) of
137 {error, Msg} ->
138
:-(
return_error(Acc, IQ, bad_request, Msg);
139 InterlocutorJID ->
140
:-(
process_reset_stanza(Acc, From, IQ, ResetStanza, InterlocutorJID)
141 end.
142
143 process_reset_stanza(Acc, From, IQ, _ResetStanza, InterlocutorJID) ->
144
:-(
ok = mod_inbox_utils:reset_unread_count_to_zero(Acc, From, InterlocutorJID),
145
:-(
Res = IQ#iq{type = result,
146 sub_el = [#xmlel{name = <<"reset">>,
147 attrs = [{<<"xmlns">>, ?NS_ESL_INBOX_CONVERSATION}],
148 children = []}]},
149
:-(
{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
:-(
CurrentTS = mongoose_acc:timestamp(Acc),
159
:-(
[Entry1] = mongoose_hooks:extend_inbox_result(Acc, [Entry0], IQ),
160
:-(
mod_inbox_utils:build_inbox_result_elements(Entry1, CurrentTS);
161 build_query_children(Acc, Entry, _IQ, only_properties) ->
162
:-(
CurrentTS = mongoose_acc:timestamp(Acc),
163
:-(
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
:-(
{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
:-(
{error, <<"no-property">>};
173 form_to_query(_, [], Acc) ->
174
:-(
Acc;
175 form_to_query(MAcc, [#xmlel{name = <<"box">>, children = [#xmlcdata{content = BoxName}]} | Rest], Acc) ->
176
:-(
case is_box_accepted(MAcc, BoxName) of
177
:-(
true -> form_to_query(MAcc, Rest, Acc#{box => BoxName});
178
:-(
false -> {error, <<"invalid-box">>}
179 end;
180 form_to_query(MAcc, [#xmlel{name = <<"archive">>, children = [#xmlcdata{content = <<"true">>}]} | Rest], Acc) ->
181
:-(
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
:-(
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
:-(
form_to_query(MAcc, Rest, Acc#{unread_count => 0});
186 form_to_query(MAcc, [#xmlel{name = <<"read">>, children = [#xmlcdata{content = <<"false">>}]} | Rest], Acc) ->
187
:-(
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
:-(
case mod_inbox_utils:maybe_binary_to_positive_integer(Value) of
191
:-(
{error, _} -> {error, <<"bad-request">>};
192 0 ->
193
:-(
form_to_query(MAcc, Rest, Acc#{muted_until => 0});
194 Num when Num > 0 ->
195
:-(
TS = mongoose_acc:timestamp(MAcc),
196
:-(
MutedUntilMicroSec = TS + erlang:convert_time_unit(Num, second, microsecond),
197
:-(
form_to_query(MAcc, Rest, Acc#{muted_until => MutedUntilMicroSec})
198 end;
199 form_to_query(_, _, _) ->
200
:-(
{error, <<"bad-request">>}.
201
202 is_box_accepted(MAcc, Box) ->
203
:-(
HostType = mongoose_acc:host_type(MAcc),
204
:-(
BoxOptions = gen_mod:get_module_opt(HostType, mod_inbox, boxes),
205
:-(
lists:member(Box, BoxOptions).
206
207 -spec should_be_stored_in_inbox(exml:element()) -> boolean().
208 should_be_stored_in_inbox(Msg) ->
209
:-(
not is_inbox_update(Msg).
210
211 -spec is_inbox_update(exml:element()) -> boolean().
212 is_inbox_update(Msg) ->
213
:-(
case exml_query:subelement_with_ns(Msg, ?NS_ESL_INBOX_CONVERSATION, undefined) of
214
:-(
undefined -> false;
215
:-(
_ -> true
216 end.
Line Hits Source