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