./ct_report/coverage/mod_inbox.COVER.html

1 %%%-------------------------------------------------------------------
2 %%% @copyright (C) 2018, Erlang-Solutions
3 %%% @doc
4 %%%
5 %%% @end
6 %%% Created : 30. Jan 2018 13:22
7 %%%-------------------------------------------------------------------
8 -module(mod_inbox).
9
10 -behaviour(gen_mod).
11 -behaviour(mongoose_module_metrics).
12
13 -include("jlib.hrl").
14 -include("mod_inbox.hrl").
15 -include("mongoose_config_spec.hrl").
16 -include("mongoose_logger.hrl").
17 -include("mongoose_ns.hrl").
18
19 -export([get_personal_data/3]).
20
21 %% gen_mod
22 -export([start/2, stop/1, config_spec/0, supported_features/0]).
23
24 -export([process_iq/5,
25 user_send_packet/4,
26 filter_local_packet/1,
27 inbox_unread_count/2,
28 remove_user/3,
29 remove_domain/3,
30 disco_local_features/1
31 ]).
32
33 -ignore_xref([
34 disco_local_features/1, filter_local_packet/1, get_personal_data/3,
35 inbox_unread_count/2, remove_domain/3, remove_user/3, user_send_packet/4
36 ]).
37
38 -export([process_inbox_boxes/1]).
39 -export([config_metrics/1]).
40
41 -type message_type() :: one2one | groupchat.
42 -type entry_key() :: {LUser :: jid:luser(),
43 LServer :: jid:lserver(),
44 ToBareJid :: jid:literal_jid()}.
45
46 -type get_inbox_params() :: #{
47 start => integer(),
48 'end' => integer(),
49 order => asc | desc,
50 hidden_read => true | false,
51 box => binary()
52 }.
53
54 -type count_res() :: ok | {ok, non_neg_integer()} | {error, term()}.
55 -type write_res() :: ok | {error, any()}.
56
57 -export_type([entry_key/0, get_inbox_params/0]).
58 -export_type([count_res/0, write_res/0, inbox_res/0]).
59
60 %%--------------------------------------------------------------------
61 %% gdpr callbacks
62 %%--------------------------------------------------------------------
63 -spec get_personal_data(gdpr:personal_data(), mongooseim:host_type(), jid:jid()) ->
64 gdpr:personal_data().
65 get_personal_data(Acc, HostType, #jid{luser = LUser, lserver = LServer}) ->
66
:-(
Schema = ["jid", "content", "unread_count", "timestamp"],
67
:-(
InboxParams = #{
68 start => 0,
69 'end' => erlang:system_time(microsecond),
70 order => asc,
71 hidden_read => false
72 },
73
:-(
Entries = mod_inbox_backend:get_inbox(HostType, LUser, LServer, InboxParams),
74
:-(
ProcessedEntries = lists:map(fun process_entry/1, Entries),
75
:-(
[{inbox, Schema, ProcessedEntries} | Acc].
76
77 process_entry(#{remote_jid := RemJID,
78 msg := Content,
79 unread_count := UnreadCount,
80 timestamp := Timestamp}) ->
81
:-(
TS = calendar:system_time_to_rfc3339(Timestamp, [{offset, "Z"}, {unit, microsecond}]),
82
:-(
{RemJID, Content, UnreadCount, TS}.
83
84 %%--------------------------------------------------------------------
85 %% gen_mod callbacks
86 %%--------------------------------------------------------------------
87
88 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
89 start(HostType, #{iqdisc := IQDisc, groupchat := MucTypes} = Opts) ->
90
:-(
mod_inbox_backend:init(HostType, Opts),
91
:-(
lists:member(muc, MucTypes) andalso mod_inbox_muc:start(HostType),
92
:-(
ejabberd_hooks:add(hooks(HostType)),
93
:-(
gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_ESL_INBOX, ejabberd_sm,
94 fun ?MODULE:process_iq/5, #{}, IQDisc),
95
:-(
gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_ESL_INBOX_CONVERSATION, ejabberd_sm,
96 fun mod_inbox_entries:process_iq_conversation/5, #{}, IQDisc),
97
:-(
start_cleaner(HostType, Opts),
98
:-(
ok.
99
100 -spec stop(HostType :: mongooseim:host_type()) -> ok.
101 stop(HostType) ->
102
:-(
gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_ESL_INBOX, ejabberd_sm),
103
:-(
gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_ESL_INBOX_CONVERSATION, ejabberd_sm),
104
:-(
ejabberd_hooks:delete(hooks(HostType)),
105
:-(
stop_cleaner(HostType),
106
:-(
mod_inbox_muc:stop(HostType),
107
:-(
case mongoose_config:get_opt([{modules, HostType}, ?MODULE, backend]) of
108
:-(
rdbms_async -> mod_inbox_rdbms_async:stop(HostType);
109
:-(
_ -> ok
110 end.
111
112 -spec supported_features() -> [atom()].
113 supported_features() ->
114
:-(
[dynamic_domains].
115
116 -spec config_spec() -> mongoose_config_spec:config_section().
117 config_spec() ->
118 166 Markers = mongoose_chat_markers:chat_marker_names(),
119 166 #section{
120 items = #{<<"backend">> => #option{type = atom, validate = {enum, [rdbms, rdbms_async]}},
121 <<"async_writer">> => async_config_spec(),
122 <<"reset_markers">> => #list{items = #option{type = binary,
123 validate = {enum, Markers}}},
124 <<"groupchat">> => #list{items = #option{type = atom,
125 validate = {enum, [muc, muclight]}}},
126 <<"boxes">> => #list{items = #option{type = binary, validate = non_empty},
127 validate = unique},
128 <<"bin_ttl">> => #option{type = integer, validate = non_negative},
129 <<"bin_clean_after">> => #option{type = integer, validate = non_negative,
130 process = fun timer:hours/1},
131 <<"aff_changes">> => #option{type = boolean},
132 <<"remove_on_kicked">> => #option{type = boolean},
133 <<"iqdisc">> => mongoose_config_spec:iqdisc()
134 },
135 defaults = #{<<"backend">> => rdbms,
136 <<"groupchat">> => [muclight],
137 <<"boxes">> => [],
138 <<"bin_ttl">> => 30, % 30 days
139 <<"bin_clean_after">> => timer:hours(1),
140 <<"aff_changes">> => true,
141 <<"remove_on_kicked">> => true,
142 <<"reset_markers">> => [<<"displayed">>],
143 <<"iqdisc">> => no_queue
144 },
145 process = fun ?MODULE:process_inbox_boxes/1
146 }.
147
148 async_config_spec() ->
149 166 #section{
150 items = #{<<"pool_size">> => #option{type = integer, validate = non_negative}},
151 defaults = #{<<"pool_size">> => 2 * erlang:system_info(schedulers_online)},
152 include = always
153 }.
154
155 process_inbox_boxes(Config = #{boxes := Boxes}) ->
156
:-(
false = lists:any(fun(<<"all">>) -> true;
157
:-(
(<<"inbox">>) -> true;
158
:-(
(<<"archive">>) -> true;
159
:-(
(<<"bin">>) -> true;
160
:-(
(_) -> false
161 end, Boxes),
162
:-(
AllBoxes = [<<"inbox">>, <<"archive">>, <<"bin">> | Boxes ],
163
:-(
Config#{boxes := AllBoxes}.
164
165 %% Cleaner gen_server callbacks
166 start_cleaner(HostType, #{bin_ttl := TTL, bin_clean_after := Interval}) ->
167
:-(
Name = gen_mod:get_module_proc(HostType, ?MODULE),
168
:-(
WOpts = #{host_type => HostType, action => fun mod_inbox_commands:flush_global_bin/2,
169 opts => TTL, interval => Interval},
170
:-(
MFA = {mongoose_collector, start_link, [Name, WOpts]},
171
:-(
ChildSpec = {Name, MFA, permanent, 5000, worker, [?MODULE]},
172
:-(
ejabberd_sup:start_child(ChildSpec).
173
174 stop_cleaner(HostType) ->
175
:-(
Name = gen_mod:get_module_proc(HostType, ?MODULE),
176
:-(
ejabberd_sup:stop_child(Name).
177
178 %%%%%%%%%%%%%%%%%%%
179 %% Process IQ
180 -spec process_iq(Acc :: mongoose_acc:t(),
181 From :: jid:jid(),
182 To :: jid:jid(),
183 IQ :: jlib:iq(),
184 Extra :: map()) -> {stop, mongoose_acc:t()} | {mongoose_acc:t(), jlib:iq()}.
185 process_iq(Acc, _From, _To, #iq{type = get, sub_el = SubEl} = IQ, #{host_type := HostType}) ->
186
:-(
Form = build_inbox_form(HostType),
187
:-(
SubElWithForm = SubEl#xmlel{ children = [Form] },
188
:-(
{Acc, IQ#iq{type = result, sub_el = SubElWithForm}};
189 process_iq(Acc, #jid{luser = LUser, lserver = LServer},
190 _To, #iq{type = set, sub_el = #xmlel{name = <<"empty-bin">>}} = IQ,
191 #{host_type := HostType}) ->
192
:-(
TS = mongoose_acc:timestamp(Acc),
193
:-(
NumRemRows = mod_inbox_backend:empty_user_bin(HostType, LServer, LUser, TS),
194
:-(
{Acc, IQ#iq{type = result, sub_el = [build_empty_bin(NumRemRows)]}};
195 process_iq(Acc, From, _To, #iq{type = set, sub_el = QueryEl} = IQ, _Extra) ->
196
:-(
HostType = mongoose_acc:host_type(Acc),
197
:-(
LUser = From#jid.luser,
198
:-(
LServer = From#jid.lserver,
199
:-(
case query_to_params(HostType, QueryEl) of
200 {error, bad_request, Msg} ->
201
:-(
{Acc, IQ#iq{type = error, sub_el = [mongoose_xmpp_errors:bad_request(<<"en">>, Msg)]}};
202 Params ->
203
:-(
List = mod_inbox_backend:get_inbox(HostType, LUser, LServer, Params),
204
:-(
forward_messages(Acc, List, IQ, From),
205
:-(
Res = IQ#iq{type = result, sub_el = [build_result_iq(List)]},
206
:-(
{Acc, Res}
207 end.
208
209 -spec forward_messages(Acc :: mongoose_acc:t(),
210 List :: [inbox_res()],
211 QueryEl :: jlib:iq(),
212 To :: jid:jid()) -> list(mongoose_acc:t()).
213 forward_messages(Acc, List, QueryEl, To) when is_list(List) ->
214
:-(
Msgs = [ build_inbox_message(Acc, El, QueryEl) || El <- List],
215
:-(
[ send_message(Acc, To, Msg) || Msg <- Msgs].
216
217 -spec send_message(mongoose_acc:t(), jid:jid(), exml:element()) -> mongoose_acc:t().
218 send_message(Acc, To = #jid{lserver = LServer}, Msg) ->
219
:-(
BareTo = jid:to_bare(To),
220
:-(
HostType = mongoose_acc:host_type(Acc),
221
:-(
NewAcc0 = mongoose_acc:new(#{location => ?LOCATION,
222 host_type => HostType,
223 lserver => LServer,
224 element => Msg,
225 from_jid => BareTo,
226 to_jid => To}),
227
:-(
PermanentFields = mongoose_acc:get_permanent_fields(Acc),
228
:-(
NewAcc = mongoose_acc:set_permanent(PermanentFields, NewAcc0),
229
:-(
ejabberd_sm:route(BareTo, To, NewAcc).
230
231 %%%%%%%%%%%%%%%%%%%
232 %% Handlers
233 -spec user_send_packet(Acc :: mongoose_acc:t(), From :: jid:jid(),
234 To :: jid:jid(), Packet :: exml:element()) ->
235 mongoose_acc:t().
236 user_send_packet(Acc, From, To, #xmlel{name = <<"message">>} = Msg) ->
237
:-(
maybe_process_message(Acc, From, To, Msg, outgoing);
238 user_send_packet(Acc, _From, _To, _Packet) ->
239
:-(
Acc.
240
241 -spec inbox_unread_count(Acc :: mongoose_acc:t(), To :: jid:jid()) -> mongoose_acc:t().
242 inbox_unread_count(Acc, To) ->
243
:-(
Res = mongoose_acc:get(inbox, unread_count, undefined, Acc),
244
:-(
get_inbox_unread(Res, Acc, To).
245
246 -spec filter_local_packet(mongoose_hooks:filter_packet_acc() | drop) ->
247 mongoose_hooks:filter_packet_acc() | drop.
248 filter_local_packet(drop) ->
249
:-(
drop;
250 filter_local_packet({From, To, Acc, Msg = #xmlel{name = <<"message">>}}) ->
251
:-(
Acc0 = maybe_process_message(Acc, From, To, Msg, incoming),
252
:-(
{From, To, Acc0, Msg};
253 filter_local_packet({From, To, Acc, Packet}) ->
254
:-(
{From, To, Acc, Packet}.
255
256 remove_user(Acc, User, Server) ->
257
:-(
HostType = mongoose_acc:host_type(Acc),
258
:-(
mod_inbox_utils:clear_inbox(HostType, User, Server),
259
:-(
Acc.
260
261 -spec remove_domain(mongoose_hooks:simple_acc(),
262 mongooseim:host_type(), jid:lserver()) ->
263 mongoose_hooks:simple_acc().
264 remove_domain(Acc, HostType, Domain) ->
265
:-(
mod_inbox_backend:remove_domain(HostType, Domain),
266
:-(
Acc.
267
268 -spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc().
269 disco_local_features(Acc = #{node := <<>>}) ->
270
:-(
mongoose_disco:add_features([?NS_ESL_INBOX], Acc);
271 disco_local_features(Acc) ->
272
:-(
Acc.
273
274 -spec maybe_process_message(Acc :: mongoose_acc:t(),
275 From :: jid:jid(),
276 To :: jid:jid(),
277 Msg :: exml:element(),
278 Dir :: mod_mam_utils:direction()) -> mongoose_acc:t().
279 maybe_process_message(Acc, From, To, Msg, Dir) ->
280
:-(
Type = mongoose_lib:get_message_type(Acc),
281
:-(
case should_be_stored_in_inbox(Acc, From, To, Msg, Dir, Type) of
282 true ->
283
:-(
do_maybe_process_message(Acc, From, To, Msg, Dir, Type);
284 false ->
285
:-(
Acc
286 end.
287
288 do_maybe_process_message(Acc, From, To, Msg, Dir, Type) ->
289 %% In case of PgSQL we can update inbox and obtain unread_count in one query,
290 %% so we put it in accumulator here.
291 %% In case of MySQL/MsSQL it costs an extra query, so we fetch it only if necessary
292 %% (when push notification is created)
293
:-(
HostType = mongoose_acc:host_type(Acc),
294
:-(
case maybe_process_acceptable_message(HostType, From, To, Msg, Acc, Dir, Type) of
295
:-(
ok -> Acc;
296 {ok, UnreadCount} ->
297
:-(
mongoose_acc:set(inbox, unread_count, UnreadCount, Acc);
298 {error, Error} ->
299
:-(
HostType = mongoose_acc:host_type(Acc),
300
:-(
?LOG_WARNING(#{what => inbox_process_message_failed,
301 from_jid => jid:to_binary(From), to_jid => jid:to_binary(To),
302
:-(
host_type => HostType, dir => incoming, reason => Error}),
303
:-(
Acc
304 end.
305
306 -spec maybe_process_acceptable_message(
307 mongooseim:host_type(), jid:jid(), jid:jid(), exml:element(),
308 mongoose_acc:t(), mod_mam_utils:direction(), message_type()) ->
309 count_res().
310 maybe_process_acceptable_message(HostType, From, To, Msg, Acc, Dir, one2one) ->
311
:-(
process_message(HostType, From, To, Msg, Acc, Dir, one2one);
312 maybe_process_acceptable_message(HostType, From, To, Msg, Acc, Dir, groupchat) ->
313
:-(
case muclight_enabled(HostType) of
314
:-(
true -> process_message(HostType, From, To, Msg, Acc, Dir, groupchat);
315
:-(
false -> ok
316 end.
317
318 -spec process_message(HostType :: mongooseim:host_type(),
319 From :: jid:jid(),
320 To :: jid:jid(),
321 Message :: exml:element(),
322 Acc :: mongoose_acc:t(),
323 Dir :: mod_mam_utils:direction(),
324 Type :: message_type()) -> count_res().
325 process_message(HostType, From, To, Message, Acc, outgoing, one2one) ->
326
:-(
mod_inbox_one2one:handle_outgoing_message(HostType, From, To, Message, Acc);
327 process_message(HostType, From, To, Message, Acc, incoming, one2one) ->
328
:-(
mod_inbox_one2one:handle_incoming_message(HostType, From, To, Message, Acc);
329 process_message(HostType, From, To, Message, Acc, outgoing, groupchat) ->
330
:-(
mod_inbox_muclight:handle_outgoing_message(HostType, From, To, Message, Acc);
331 process_message(HostType, From, To, Message, Acc, incoming, groupchat) ->
332
:-(
mod_inbox_muclight:handle_incoming_message(HostType, From, To, Message, Acc);
333 process_message(HostType, From, To, Message, _TS, Dir, Type) ->
334
:-(
?LOG_WARNING(#{what => inbox_unknown_message,
335 text => <<"Unknown message was not written into inbox">>,
336 exml_packet => Message,
337 from_jid => jid:to_binary(From), to_jid => jid:to_binary(To),
338
:-(
host_type => HostType, dir => Dir, inbox_message_type => Type}),
339
:-(
ok.
340
341
342 %%%%%%%%%%%%%%%%%%%
343 %% Stanza builders
344 build_empty_bin(Num) ->
345
:-(
#xmlel{name = <<"empty-bin">>,
346 attrs = [{<<"xmlns">>, ?NS_ESL_INBOX}],
347 children = [#xmlel{name = <<"num">>,
348 children = [#xmlcdata{content = integer_to_binary(Num)}]}]}.
349
350 -spec build_inbox_message(mongoose_acc:t(), inbox_res(), jlib:iq()) -> exml:element().
351 build_inbox_message(Acc, InboxRes, IQ) ->
352
:-(
#xmlel{name = <<"message">>, attrs = [{<<"id">>, mongoose_bin:gen_from_timestamp()}],
353 children = [build_result_el(Acc, InboxRes, IQ)]}.
354
355 -spec build_result_el(mongoose_acc:t(), inbox_res(), jlib:iq()) -> exml:element().
356 build_result_el(Acc, InboxRes = #{unread_count := Count}, IQ = #iq{id = IqId, sub_el = QueryEl}) ->
357
:-(
AccTS = mongoose_acc:timestamp(Acc),
358
:-(
Forwarded = mod_inbox_utils:build_forward_el(InboxRes),
359
:-(
Properties = mod_inbox_entries:extensions_result(InboxRes, AccTS),
360
:-(
Extensions = mongoose_hooks:extend_inbox_message(Acc, InboxRes, IQ),
361
:-(
#xmlel{name = <<"result">>,
362 attrs = [{<<"xmlns">>, ?NS_ESL_INBOX},
363 {<<"unread">>, integer_to_binary(Count)},
364 {<<"queryid">>, exml_query:attr(QueryEl, <<"queryid">>, IqId)}],
365 children = [Forwarded | Properties] ++ Extensions}.
366
367 -spec build_result_iq([inbox_res()]) -> exml:element().
368 build_result_iq(List) ->
369
:-(
AllUnread = [ N || #{unread_count := N} <- List, N =/= 0],
370
:-(
Result = #{<<"count">> => length(List),
371 <<"unread-messages">> => lists:sum(AllUnread),
372 <<"active-conversations">> => length(AllUnread)},
373
:-(
ResultBinary = maps:map(fun(K, V) ->
374
:-(
#xmlel{name = K, children = [#xmlcdata{content = integer_to_binary(V)}]}
375 end, Result),
376
:-(
#xmlel{name = <<"fin">>, attrs = [{<<"xmlns">>, ?NS_ESL_INBOX}],
377 children = maps:values(ResultBinary)}.
378
379 %%%%%%%%%%%%%%%%%%%
380 %% iq-get
381 -spec build_inbox_form(mongooseim:host_type()) -> exml:element().
382 build_inbox_form(HostType) ->
383
:-(
AllBoxes = mod_inbox_utils:all_valid_boxes_for_query(HostType),
384
:-(
OrderOptions = [
385 {<<"Ascending by timestamp">>, <<"asc">>},
386 {<<"Descending by timestamp">>, <<"desc">>}
387 ],
388
:-(
FormFields = [
389 jlib:form_field({<<"FORM_TYPE">>, <<"hidden">>, ?NS_ESL_INBOX}),
390 text_single_form_field(<<"start">>),
391 text_single_form_field(<<"end">>),
392 text_single_form_field(<<"hidden_read">>, <<"false">>),
393 mod_inbox_utils:list_single_form_field(<<"order">>, <<"desc">>, OrderOptions),
394 mod_inbox_utils:list_single_form_field(<<"box">>, <<"all">>, AllBoxes),
395 jlib:form_field({<<"archive">>, <<"boolean">>, <<"false">>})
396 ],
397
:-(
#xmlel{name = <<"x">>,
398 attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}],
399 children = FormFields}.
400
401 -spec text_single_form_field(Var :: binary()) -> exml:element().
402 text_single_form_field(Var) ->
403
:-(
#xmlel{name = <<"field">>, attrs = [{<<"var">>, Var}, {<<"type">>, <<"text-single">>}]}.
404
405 -spec text_single_form_field(Var :: binary(), DefaultValue :: binary()) -> exml:element().
406 text_single_form_field(Var, DefaultValue) ->
407
:-(
#xmlel{name = <<"field">>,
408 attrs = [{<<"var">>, Var}, {<<"type">>, <<"text-single">>}, {<<"value">>, DefaultValue}]}.
409
410 %%%%%%%%%%%%%%%%%%%
411 %% iq-set
412 -spec query_to_params(mongooseim:host_type(), QueryEl :: exml:element()) ->
413 get_inbox_params() | {error, bad_request, Msg :: binary()}.
414 query_to_params(HostType, QueryEl) ->
415
:-(
case form_to_params(HostType, exml_query:subelement_with_ns(QueryEl, ?NS_XDATA)) of
416 {error, bad_request, Msg} ->
417
:-(
{error, bad_request, Msg};
418 Params ->
419
:-(
case maybe_rsm(exml_query:subelement_with_ns(QueryEl, ?NS_RSM)) of
420
:-(
{error, Msg} -> {error, bad_request, Msg};
421
:-(
undefined -> Params;
422
:-(
Rsm -> Params#{limit => Rsm}
423 end
424 end.
425
426 -spec maybe_rsm(exml:element() | undefined) ->
427 undefined | non_neg_integer() | {error, binary()}.
428 maybe_rsm(#xmlel{name = <<"set">>,
429 children = [#xmlel{name = <<"max">>,
430 children = [#xmlcdata{content = Bin}]}]}) ->
431
:-(
case mod_inbox_utils:maybe_binary_to_positive_integer(Bin) of
432
:-(
{error, _} -> {error, wrong_rsm_message()};
433
:-(
0 -> undefined;
434
:-(
N -> N
435 end;
436 maybe_rsm(undefined) ->
437
:-(
undefined;
438 maybe_rsm(_) ->
439
:-(
{error, wrong_rsm_message()}.
440
441 wrong_rsm_message() ->
442
:-(
<<"bad-request">>.
443
444 -spec form_to_params(mongooseim:host_type(), FormEl :: exml:element() | undefined) ->
445 get_inbox_params() | {error, bad_request, Msg :: binary()}.
446 form_to_params(_, undefined) ->
447
:-(
#{ order => desc };
448 form_to_params(HostType, FormEl) ->
449
:-(
ParsedFields = jlib:parse_xdata_fields(exml_query:subelements(FormEl, <<"field">>)),
450
:-(
?LOG_DEBUG(#{what => inbox_parsed_form_fields, parsed_fields => ParsedFields}),
451
:-(
fields_to_params(HostType, ParsedFields, #{ order => desc }).
452
453 -spec fields_to_params(mongooseim:host_type(),
454 [{Var :: binary(), Values :: [binary()]}], Acc :: get_inbox_params()) ->
455 get_inbox_params() | {error, bad_request, Msg :: binary()}.
456 fields_to_params(_, [], Acc) ->
457
:-(
Acc;
458 fields_to_params(HostType, [{<<"start">>, [StartISO]} | RFields], Acc) ->
459
:-(
try calendar:rfc3339_to_system_time(binary_to_list(StartISO), [{unit, microsecond}]) of
460 StartStamp ->
461
:-(
fields_to_params(HostType, RFields, Acc#{ start => StartStamp })
462 catch error:Error ->
463
:-(
?LOG_WARNING(#{what => inbox_invalid_form_field,
464
:-(
reason => Error, field => start, value => StartISO}),
465
:-(
{error, bad_request, invalid_field_value(<<"start">>, StartISO)}
466 end;
467 fields_to_params(HostType, [{<<"end">>, [EndISO]} | RFields], Acc) ->
468
:-(
try calendar:rfc3339_to_system_time(binary_to_list(EndISO), [{unit, microsecond}]) of
469 EndStamp ->
470
:-(
fields_to_params(HostType, RFields, Acc#{ 'end' => EndStamp })
471 catch error:Error ->
472
:-(
?LOG_WARNING(#{what => inbox_invalid_form_field,
473
:-(
reason => Error, field => 'end', value => EndISO}),
474
:-(
{error, bad_request, invalid_field_value(<<"end">>, EndISO)}
475 end;
476 fields_to_params(HostType, [{<<"order">>, [OrderBin]} | RFields], Acc) ->
477
:-(
case binary_to_order(OrderBin) of
478 error ->
479
:-(
?LOG_WARNING(#{what => inbox_invalid_form_field,
480
:-(
field => order, value => OrderBin}),
481
:-(
{error, bad_request, invalid_field_value(<<"order">>, OrderBin)};
482 Order ->
483
:-(
fields_to_params(HostType, RFields, Acc#{ order => Order })
484 end;
485
486 fields_to_params(HostType, [{<<"hidden_read">>, [HiddenRead]} | RFields], Acc) ->
487
:-(
case mod_inbox_utils:binary_to_bool(HiddenRead) of
488 error ->
489
:-(
?LOG_WARNING(#{what => inbox_invalid_form_field,
490
:-(
field => hidden_read, value => HiddenRead}),
491
:-(
{error, bad_request, invalid_field_value(<<"hidden_read">>, HiddenRead)};
492 Hidden ->
493
:-(
fields_to_params(HostType, RFields, Acc#{ hidden_read => Hidden })
494 end;
495
496 fields_to_params(HostType, [{<<"archive">>, [Value]} | RFields], Acc) ->
497
:-(
case mod_inbox_utils:binary_to_bool(Value) of
498 error ->
499
:-(
?LOG_WARNING(#{what => inbox_invalid_form_field,
500
:-(
field => archive, value => Value}),
501
:-(
{error, bad_request, invalid_field_value(<<"archive">>, Value)};
502 true ->
503
:-(
fields_to_params(HostType, RFields, Acc#{ box => maps:get(box, Acc, <<"archive">>) });
504 false ->
505
:-(
fields_to_params(HostType, RFields, Acc#{ box => maps:get(box, Acc, <<"inbox">>) })
506 end;
507
508 fields_to_params(HostType, [{<<"box">>, [Value]} | RFields], Acc) ->
509
:-(
case validate_box(HostType, Value) of
510 false ->
511
:-(
?LOG_WARNING(#{what => inbox_invalid_form_field,
512
:-(
field => box, value => Value}),
513
:-(
{error, bad_request, invalid_field_value(<<"box">>, Value)};
514 true ->
515
:-(
fields_to_params(HostType, RFields, Acc#{ box => Value })
516 end;
517
518 fields_to_params(HostType, [{<<"FORM_TYPE">>, _} | RFields], Acc) ->
519
:-(
fields_to_params(HostType, RFields, Acc);
520 fields_to_params(_, [{Invalid, [InvalidFieldVal]} | _], _) ->
521
:-(
?LOG_WARNING(#{what => inbox_invalid_form_field, reason => unknown_field,
522
:-(
field => Invalid, value => InvalidFieldVal}),
523
:-(
{error, bad_request, <<"Unknown inbox form field=", Invalid/binary, ", value=", InvalidFieldVal/binary>>}.
524
525 -spec binary_to_order(binary()) -> asc | desc | error.
526
:-(
binary_to_order(<<"desc">>) -> desc;
527
:-(
binary_to_order(<<"asc">>) -> asc;
528
:-(
binary_to_order(_) -> error.
529
530 validate_box(HostType, Box) ->
531
:-(
AllBoxes = mod_inbox_utils:all_valid_boxes_for_query(HostType),
532
:-(
lists:member(Box, AllBoxes).
533
534 invalid_field_value(Field, Value) ->
535
:-(
<<"Invalid inbox form field value, field=", Field/binary, ", value=", Value/binary>>.
536
537 %%%%%%%%%%%%%%%%%%%
538 %% Helpers
539 get_inbox_unread(Value, Acc, _) when is_integer(Value) ->
540
:-(
Acc;
541 get_inbox_unread(undefined, Acc, To) ->
542 %% TODO this value should be bound to a stanza reference inside Acc
543
:-(
InterlocutorJID = mongoose_acc:from_jid(Acc),
544
:-(
InboxEntryKey = mod_inbox_utils:build_inbox_entry_key(To, InterlocutorJID),
545
:-(
HostType = mongoose_acc:host_type(Acc),
546
:-(
{ok, Count} = mod_inbox_backend:get_inbox_unread(HostType, InboxEntryKey),
547
:-(
mongoose_acc:set(inbox, unread_count, Count, Acc).
548
549 hooks(HostType) ->
550
:-(
[
551 {remove_user, HostType, ?MODULE, remove_user, 50},
552 {remove_domain, HostType, ?MODULE, remove_domain, 50},
553 {user_send_packet, HostType, ?MODULE, user_send_packet, 70},
554 {filter_local_packet, HostType, ?MODULE, filter_local_packet, 90},
555 {inbox_unread_count, HostType, ?MODULE, inbox_unread_count, 80},
556 {get_personal_data, HostType, ?MODULE, get_personal_data, 50},
557 {disco_local_features, HostType, ?MODULE, disco_local_features, 99}
558 ].
559
560 get_groupchat_types(HostType) ->
561
:-(
gen_mod:get_module_opt(HostType, ?MODULE, groupchat).
562
563 -spec config_metrics(mongooseim:host_type()) -> [{gen_mod:opt_key(), gen_mod:opt_value()}].
564 config_metrics(HostType) ->
565
:-(
mongoose_module_metrics:opts_for_module(HostType, ?MODULE, [backend]).
566
567 -spec muclight_enabled(HostType :: mongooseim:host_type()) -> boolean().
568 muclight_enabled(HostType) ->
569
:-(
Groupchats = get_groupchat_types(HostType),
570
:-(
lists:member(muclight, Groupchats).
571
572 %%%%%%%%%%%%%%%%%%%
573 %% Message Predicates
574 -spec should_be_stored_in_inbox(
575 mongoose_acc:t(), jid:jid(), jid:jid(), exml:element(), mod_mam_utils:direction(), message_type()) ->
576 boolean().
577 should_be_stored_in_inbox(Acc, From, To, Msg, Dir, Type) ->
578
:-(
mod_mam_utils:is_archivable_message(?MODULE, Dir, Msg, true)
579
:-(
andalso mod_inbox_entries:should_be_stored_in_inbox(Msg)
580
:-(
andalso inbox_owner_exists(Acc, From, To, Dir, Type).
581
582 -spec inbox_owner_exists(mongoose_acc:t(),
583 From :: jid:jid(),
584 To ::jid:jid(),
585 mod_mam_utils:direction(),
586 message_type()) -> boolean().
587 inbox_owner_exists(Acc, _, To, incoming, MessageType) -> % filter_local_packet
588
:-(
HostType = mongoose_acc:host_type(Acc),
589
:-(
mongoose_lib:does_local_user_exist(HostType, To, MessageType);
590 inbox_owner_exists(Acc, From, _, outgoing, _) -> % user_send_packet
591
:-(
HostType = mongoose_acc:host_type(Acc),
592
:-(
ejabberd_auth:does_user_exist(HostType, From, stored).
Line Hits Source