./ct_report/coverage/mod_inbox_entries.COVER.html

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