1 |
|
%%%---------------------------------------------------------------------------- |
2 |
|
%%% @doc |
3 |
|
%%% RDBMS backend for mod_smart_markers |
4 |
|
%%% @end |
5 |
|
%%% @copyright (C) 2020-2022, Erlang Solutions Ltd. |
6 |
|
%%%---------------------------------------------------------------------------- |
7 |
|
-module(mod_smart_markers_rdbms). |
8 |
|
-author("denysgonchar"). |
9 |
|
-behavior(mod_smart_markers_backend). |
10 |
|
|
11 |
|
-include("jlib.hrl"). |
12 |
|
|
13 |
|
-export([init/2, update_chat_marker/2, get_chat_markers/4]). |
14 |
|
-export([get_conv_chat_marker/6]). |
15 |
|
-export([remove_domain/2, remove_user/2, remove_to/2, remove_to_for_user/3]). |
16 |
|
-export([encode_jid/1, encode_thread/1, encode_type/1, check_upsert_result/1]). |
17 |
|
|
18 |
|
%%-------------------------------------------------------------------- |
19 |
|
%% API |
20 |
|
%%-------------------------------------------------------------------- |
21 |
|
-spec init(mongooseim:host_type(), gen_mod:module_opts()) -> ok. |
22 |
|
init(HostType, _) -> |
23 |
8 |
KeyFields = [<<"lserver">>, <<"luser">>, <<"to_jid">>, <<"thread">>, <<"type">>], |
24 |
8 |
UpdateFields = [<<"msg_id">>, <<"timestamp">>], |
25 |
8 |
InsertFields = KeyFields ++ UpdateFields, |
26 |
8 |
rdbms_queries:prepare_upsert(HostType, smart_markers_upsert, smart_markers, |
27 |
|
InsertFields, UpdateFields, KeyFields, <<"timestamp">>), |
28 |
8 |
mongoose_rdbms:prepare(smart_markers_select_conv, smart_markers, |
29 |
|
[lserver, luser, to_jid, thread, timestamp], |
30 |
|
<<"SELECT lserver, luser, to_jid, thread, type, msg_id, timestamp FROM smart_markers " |
31 |
|
"WHERE lserver = ? AND luser = ? AND to_jid = ? AND thread = ? AND timestamp >= ?">>), |
32 |
8 |
mongoose_rdbms:prepare(smart_markers_select, smart_markers, |
33 |
|
[to_jid, thread, timestamp], |
34 |
|
<<"SELECT lserver, luser, type, msg_id, timestamp FROM smart_markers " |
35 |
|
"WHERE to_jid = ? AND thread = ? AND timestamp >= ?">>), |
36 |
8 |
mongoose_rdbms:prepare(markers_remove_domain, smart_markers, |
37 |
|
[lserver], <<"DELETE FROM smart_markers WHERE lserver=?">>), |
38 |
8 |
mongoose_rdbms:prepare(markers_remove_user, smart_markers, |
39 |
|
[lserver, luser], <<"DELETE FROM smart_markers WHERE lserver=? AND luser=?">>), |
40 |
8 |
mongoose_rdbms:prepare(markers_remove_to, smart_markers, |
41 |
|
[to_jid], <<"DELETE FROM smart_markers WHERE to_jid=?">>), |
42 |
8 |
mongoose_rdbms:prepare(markers_remove_to_for_user, smart_markers, |
43 |
|
[lserver, luser, to_jid], |
44 |
|
<<"DELETE FROM smart_markers WHERE lserver=? AND luser=? AND to_jid=?">>), |
45 |
8 |
ok. |
46 |
|
|
47 |
|
%%% @doc |
48 |
|
%%% 'from', 'to', 'thread' and 'type' keys of the ChatMarker map serve |
49 |
|
%%% as a composite database key. If key is not available in the database, |
50 |
|
%%% then chat marker must be added. Otherwise this function must update |
51 |
|
%%% chat marker record for that composite key. |
52 |
|
%%% @end |
53 |
|
-spec update_chat_marker(mongooseim:host_type(), |
54 |
|
mod_smart_markers:chat_marker()) -> ok. |
55 |
|
update_chat_marker(HostType, #{from := #jid{luser = LU, lserver = LS}, |
56 |
|
to := To, thread := Thread, |
57 |
|
type := Type, timestamp := TS, id := Id}) -> |
58 |
28 |
ToEncoded = encode_jid(To), |
59 |
28 |
ThreadEncoded = encode_thread(Thread), |
60 |
28 |
TypeEncoded = encode_type(Type), |
61 |
28 |
KeyValues = [LS, LU, ToEncoded, ThreadEncoded, TypeEncoded], |
62 |
28 |
UpdateValues = [Id, TS], |
63 |
28 |
InsertValues = KeyValues ++ UpdateValues, |
64 |
28 |
Res = rdbms_queries:execute_upsert(HostType, smart_markers_upsert, |
65 |
|
InsertValues, UpdateValues, KeyValues), |
66 |
28 |
ok = check_upsert_result(Res). |
67 |
|
|
68 |
|
-spec get_conv_chat_marker(HostType :: mongooseim:host_type(), |
69 |
|
From :: jid:jid(), |
70 |
|
To :: jid:jid(), |
71 |
|
Thread :: mod_smart_markers:maybe_thread(), |
72 |
|
Timestamp :: integer(), |
73 |
|
Private :: boolean()) -> [mod_smart_markers:chat_marker()]. |
74 |
|
get_conv_chat_marker(HostType, From, To = #jid{lserver = ToLServer}, Thread, TS, Private) -> |
75 |
|
% If To is a room, we'll want to check just the room |
76 |
20 |
case mongoose_domain_api:get_subdomain_host_type(ToLServer) of |
77 |
|
{error, not_found} -> |
78 |
14 |
one2one_get_conv_chat_marker(HostType, From, To, Thread, TS, Private); |
79 |
|
{ok, _} -> |
80 |
6 |
groupchat_get_conv_chat_marker(HostType, From, To, Thread, TS, Private) |
81 |
|
end. |
82 |
|
|
83 |
|
one2one_get_conv_chat_marker(HostType, |
84 |
|
From = #jid{luser = FromLUser, lserver = FromLServer}, |
85 |
|
To = #jid{luser = ToLUser, lserver = ToLServer}, |
86 |
|
Thread, TS, Private) -> |
87 |
14 |
{selected, ChatMarkersFrom} = mongoose_rdbms:execute_successfully( |
88 |
|
HostType, smart_markers_select_conv, |
89 |
|
[FromLServer, FromLUser, encode_jid(To), encode_thread(Thread), TS]), |
90 |
14 |
ChatMarkers = case Private of |
91 |
4 |
true -> ChatMarkersFrom; |
92 |
|
false -> |
93 |
10 |
{selected, ChatMarkersTo} = mongoose_rdbms:execute_successfully( |
94 |
|
HostType, smart_markers_select_conv, |
95 |
|
[ToLServer, ToLUser, encode_jid(From), encode_thread(Thread), TS]), |
96 |
10 |
ChatMarkersFrom ++ ChatMarkersTo |
97 |
|
end, |
98 |
14 |
[ decode_chat_marker(Tuple) || Tuple <- ChatMarkers]. |
99 |
|
|
100 |
|
groupchat_get_conv_chat_marker(HostType, _From, To, Thread, TS, false) -> |
101 |
2 |
get_chat_markers(HostType, To, Thread, TS); |
102 |
|
groupchat_get_conv_chat_marker(HostType, #jid{luser = FromLUser, lserver = FromLServer}, To, Thread, TS, true) -> |
103 |
4 |
{selected, ChatMarkers} = mongoose_rdbms:execute_successfully( |
104 |
|
HostType, smart_markers_select_conv, |
105 |
|
[FromLServer, FromLUser, encode_jid(To), encode_thread(Thread), TS]), |
106 |
4 |
[ decode_chat_marker(Tuple) || Tuple <- ChatMarkers]. |
107 |
|
|
108 |
|
%%% @doc |
109 |
|
%%% This function must return the latest chat markers sent to the |
110 |
|
%%% user/room (with or w/o thread) later than provided timestamp. |
111 |
|
%%% @end |
112 |
|
-spec get_chat_markers(HostType :: mongooseim:host_type(), |
113 |
|
To :: jid:jid(), |
114 |
|
Thread :: mod_smart_markers:maybe_thread(), |
115 |
|
Timestamp :: integer()) -> [mod_smart_markers:chat_marker()]. |
116 |
|
get_chat_markers(HostType, To, Thread, TS) -> |
117 |
27 |
{selected, ChatMarkers} = mongoose_rdbms:execute_successfully( |
118 |
|
HostType, smart_markers_select, |
119 |
|
[encode_jid(To), encode_thread(Thread), TS]), |
120 |
27 |
[ #{from => jid:make_noprep(CLUser, CLServer, <<>>), |
121 |
|
to => To, |
122 |
|
thread => Thread, |
123 |
|
type => decode_type(CType), |
124 |
|
timestamp => decode_timestamp(CTS), |
125 |
|
id => CMsgId} |
126 |
27 |
|| {CLServer, CLUser, CType, CMsgId, CTS} <- ChatMarkers]. |
127 |
|
|
128 |
|
|
129 |
|
-spec remove_domain(mongooseim:host_type(), jid:lserver()) -> mongoose_rdbms:query_result(). |
130 |
|
remove_domain(HostType, Domain) -> |
131 |
1 |
mongoose_rdbms:execute_successfully(HostType, markers_remove_domain, [Domain]). |
132 |
|
|
133 |
|
-spec remove_user(mongooseim:host_type(), jid:jid()) -> mongoose_rdbms:query_result(). |
134 |
|
remove_user(HostType, #jid{luser = LU, lserver = LS}) -> |
135 |
64 |
mongoose_rdbms:execute_successfully(HostType, markers_remove_user, [LS, LU]). |
136 |
|
|
137 |
|
-spec remove_to(mongooseim:host_type(), jid:jid()) -> mongoose_rdbms:query_result(). |
138 |
|
remove_to(HostType, To) -> |
139 |
4 |
mongoose_rdbms:execute_successfully(HostType, markers_remove_to, [encode_jid(To)]). |
140 |
|
|
141 |
|
-spec remove_to_for_user(mongooseim:host_type(), From :: jid:jid(), To :: jid:jid()) -> |
142 |
|
mongoose_rdbms:query_result(). |
143 |
|
remove_to_for_user(HostType, #jid{luser = LU, lserver = LS}, To) -> |
144 |
2 |
mongoose_rdbms:execute_successfully(HostType, markers_remove_to_for_user, [LS, LU, encode_jid(To)]). |
145 |
|
|
146 |
|
%%-------------------------------------------------------------------- |
147 |
|
%% local functions |
148 |
|
%%-------------------------------------------------------------------- |
149 |
106 |
encode_jid(JID) -> jid:to_binary(jid:to_lus(JID)). |
150 |
|
|
151 |
82 |
encode_thread(undefined) -> <<>>; |
152 |
18 |
encode_thread(Thread) -> Thread. |
153 |
|
|
154 |
8 |
encode_type(received) -> <<"R">>; |
155 |
37 |
encode_type(displayed) -> <<"D">>; |
156 |
:-( |
encode_type(acknowledged) -> <<"A">>. |
157 |
|
|
158 |
|
%% MySQL returns 1 when an upsert is an insert |
159 |
|
%% and 2, when an upsert acts as update |
160 |
45 |
check_upsert_result({updated, 1}) -> ok; |
161 |
:-( |
check_upsert_result({updated, 2}) -> ok; |
162 |
|
check_upsert_result(Result) -> |
163 |
:-( |
{error, {bad_result, Result}}. |
164 |
|
|
165 |
|
decode_chat_marker({LS, LU, ToJid, MsgThread, Type, MsgId, MsgTS}) -> |
166 |
20 |
#{from => jid:make_noprep(LU, LS, <<>>), |
167 |
|
to => decode_jid(ToJid), |
168 |
|
thread => decode_thread(MsgThread), |
169 |
|
type => decode_type(Type), |
170 |
|
timestamp => decode_timestamp(MsgTS), |
171 |
|
id => MsgId}. |
172 |
|
|
173 |
20 |
decode_jid(EncodedJID) -> jid:from_binary(EncodedJID). |
174 |
|
|
175 |
16 |
decode_thread(<<>>) -> undefined; |
176 |
4 |
decode_thread(Thread) -> Thread. |
177 |
|
|
178 |
4 |
decode_type(<<"R">>) -> received; |
179 |
35 |
decode_type(<<"D">>) -> displayed; |
180 |
:-( |
decode_type(<<"A">>) -> acknowledged. |
181 |
|
|
182 |
|
decode_timestamp(EncodedTS) -> |
183 |
39 |
mongoose_rdbms:result_to_integer(EncodedTS). |