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