1 |
|
-module(mod_muc_rdbms). |
2 |
|
-include("mod_muc.hrl"). |
3 |
|
-include("mongoose_logger.hrl"). |
4 |
|
|
5 |
|
-export([init/2, |
6 |
|
store_room/4, |
7 |
|
restore_room/3, |
8 |
|
forget_room/3, |
9 |
|
get_rooms/2, |
10 |
|
can_use_nick/4, |
11 |
|
get_nick/3, |
12 |
|
set_nick/4, |
13 |
|
unset_nick/3, |
14 |
|
remove_domain/3 |
15 |
|
]). |
16 |
|
|
17 |
|
-ignore_xref([can_use_nick/4, forget_room/3, get_nick/3, get_rooms/2, remove_domain/3, init/2, |
18 |
|
restore_room/3, set_nick/4, store_room/4, unset_nick/3]). |
19 |
|
|
20 |
|
-import(mongoose_rdbms, [prepare/4, execute_successfully/3]). |
21 |
|
|
22 |
|
%% Host of MUC service |
23 |
|
-type muc_host() :: jid:server(). |
24 |
|
|
25 |
|
%% User's JID. Can be on another domain accessible over FED. |
26 |
|
%% Only bare part (user@host) is important. |
27 |
|
-type client_jid() :: jid:jid(). |
28 |
|
-type room_id() :: pos_integer(). |
29 |
|
-type room_opts() :: [{OptionName :: atom(), OptionValue :: term()}]. |
30 |
|
-type aff() :: atom(). |
31 |
|
-type updated_result() :: {updated, non_neg_integer()}. |
32 |
|
|
33 |
|
|
34 |
|
-spec init(mongooseim:host_type(), ModuleOpts :: list()) -> ok. |
35 |
|
init(HostType, _Opts) -> |
36 |
14 |
prepare_queries(HostType), |
37 |
14 |
ok. |
38 |
|
|
39 |
|
prepare_queries(HostType) -> |
40 |
|
%% Queries to muc_rooms table |
41 |
14 |
prepare(muc_insert_room, muc_rooms, |
42 |
|
[muc_host, room_name, options], |
43 |
|
<<"INSERT INTO muc_rooms (muc_host, room_name, options)" |
44 |
|
" VALUES (?, ?, ?)">>), |
45 |
14 |
prepare(muc_select_room_id, muc_rooms, |
46 |
|
[muc_host, room_name], |
47 |
|
<<"SELECT id FROM muc_rooms " |
48 |
|
"WHERE muc_host = ? AND room_name = ?">>), |
49 |
14 |
prepare(muc_select_room, muc_rooms, |
50 |
|
[muc_host, room_name], |
51 |
|
<<"SELECT id, options FROM muc_rooms " |
52 |
|
"WHERE muc_host = ? AND room_name = ?">>), |
53 |
14 |
prepare(muc_delete_room, muc_rooms, |
54 |
|
[muc_host, room_name], |
55 |
|
<<"DELETE FROM muc_rooms WHERE muc_host = ? AND room_name = ?">>), |
56 |
14 |
prepare(muc_rooms_remove_domain, muc_rooms, |
57 |
|
[muc_host], |
58 |
|
<<"DELETE FROM muc_rooms WHERE muc_host = ?">>), |
59 |
14 |
prepare(muc_select_rooms, muc_rooms, [muc_host], |
60 |
|
<<"SELECT id, room_name, options FROM muc_rooms WHERE muc_host = ?">>), |
61 |
|
%% Queries to muc_room_aff table |
62 |
14 |
prepare(muc_insert_aff, muc_room_aff, |
63 |
|
[room_id, luser, lserver, resource, aff], |
64 |
|
<<"INSERT INTO muc_room_aff" |
65 |
|
" (room_id, luser, lserver, resource, aff)" |
66 |
|
" VALUES(?, ?, ?, ?, ?)">>), |
67 |
14 |
prepare(muc_select_aff, muc_room_aff, |
68 |
|
[room_id], |
69 |
|
<<"SELECT luser, lserver, resource, aff " |
70 |
|
"FROM muc_room_aff WHERE room_id = ?">>), |
71 |
14 |
prepare(muc_delete_aff, muc_room_aff, [room_id], |
72 |
|
<<"DELETE FROM muc_room_aff WHERE room_id = ?">>), |
73 |
14 |
prepare(muc_room_aff_remove_room_domain, muc_room_aff, |
74 |
|
['muc_rooms.muc_host'], |
75 |
|
<<"DELETE FROM muc_room_aff WHERE room_id IN " |
76 |
|
"(SELECT id FROM muc_rooms WHERE muc_host = ?)">>), |
77 |
14 |
prepare(muc_room_aff_remove_user_domain, muc_room_aff, |
78 |
|
[lserver], |
79 |
|
<<"DELETE FROM muc_room_aff WHERE lserver = ?">>), |
80 |
|
%% Queries to muc_registered table |
81 |
14 |
prepare(muc_select_nick_user, muc_registered, |
82 |
|
[muc_host, lserver, nick], |
83 |
|
<<"SELECT luser FROM muc_registered WHERE muc_host = ?" |
84 |
|
" AND lserver = ? AND nick = ?">>), |
85 |
14 |
prepare(muc_select_nick, muc_registered, |
86 |
|
[muc_host, lserver, luser], |
87 |
|
<<"SELECT nick FROM muc_registered WHERE muc_host = ?" |
88 |
|
" AND lserver = ? AND luser = ?">>), |
89 |
14 |
prepare(muc_delete_nick, muc_registered, |
90 |
|
[muc_host, lserver, luser], |
91 |
|
<<"DELETE FROM muc_registered WHERE muc_host = ?" |
92 |
|
" AND lserver = ? AND luser = ?">>), |
93 |
14 |
prepare(muc_registered_remove_room_domain, muc_registered, |
94 |
|
[muc_host], |
95 |
|
<<"DELETE FROM muc_registered WHERE muc_host = ?">>), |
96 |
14 |
prepare(muc_registered_remove_user_domain, muc_registered, |
97 |
|
[lserver], |
98 |
|
<<"DELETE FROM muc_room_aff WHERE lserver = ?">>), |
99 |
14 |
rdbms_queries:prepare_upsert(HostType, muc_nick_upsert, muc_registered, |
100 |
|
[<<"muc_host">>, <<"luser">>, <<"lserver">>, <<"nick">>], |
101 |
|
[<<"nick">>], |
102 |
|
[<<"muc_host">>, <<"luser">>, <<"lserver">>]), |
103 |
14 |
ok. |
104 |
|
|
105 |
|
%% Room API functions |
106 |
|
|
107 |
|
-spec remove_domain(mongooseim:host_type(), muc_host(), jid:lserver()) -> ok. |
108 |
|
remove_domain(HostType, MucHost, Domain) -> |
109 |
1 |
F = fun() -> |
110 |
1 |
mongoose_rdbms:execute_successfully( |
111 |
|
HostType, muc_registered_remove_room_domain, [MucHost]), |
112 |
1 |
mongoose_rdbms:execute_successfully( |
113 |
|
HostType, muc_registered_remove_user_domain, [Domain]), |
114 |
1 |
mongoose_rdbms:execute_successfully( |
115 |
|
HostType, muc_room_aff_remove_room_domain, [MucHost]), |
116 |
1 |
mongoose_rdbms:execute_successfully( |
117 |
|
HostType, muc_room_aff_remove_user_domain, [Domain]), |
118 |
1 |
mongoose_rdbms:execute_successfully( |
119 |
|
HostType, muc_rooms_remove_domain, [MucHost]), |
120 |
1 |
ok |
121 |
|
end, |
122 |
1 |
{atomic, ok} = mongoose_rdbms:sql_transaction(HostType, F), |
123 |
1 |
ok. |
124 |
|
|
125 |
|
-spec store_room(mongooseim:host_type(), muc_host(), mod_muc:room(), room_opts()) -> |
126 |
|
ok | {error, term()}. |
127 |
|
store_room(HostType, MucHost, RoomName, Opts) -> |
128 |
305 |
Affs = proplists:get_value(affiliations, Opts), |
129 |
305 |
NewOpts = proplists:delete(affiliations, Opts), |
130 |
305 |
ExtOpts = jiffy:encode({NewOpts}), |
131 |
305 |
F = fun() -> |
132 |
305 |
forget_room_transaction(HostType, MucHost, RoomName), |
133 |
305 |
store_room_transaction(HostType, MucHost, RoomName, ExtOpts, Affs) |
134 |
|
end, |
135 |
305 |
{atomic, Res} = mongoose_rdbms:sql_transaction(HostType, F), |
136 |
305 |
Res. |
137 |
|
|
138 |
|
-spec restore_room(mongooseim:host_type(), muc_host(), mod_muc:room()) -> |
139 |
|
{ok, room_opts()} | {error, room_not_found} | {error, term()}. |
140 |
|
restore_room(HostType, MucHost, RoomName) -> |
141 |
50 |
case execute_select_room(HostType, MucHost, RoomName) of |
142 |
|
{selected, [{ExtRoomID, ExtOpts}]} -> |
143 |
8 |
RoomID = mongoose_rdbms:result_to_integer(ExtRoomID), |
144 |
8 |
FullOpts = get_full_options(HostType, ExtOpts, RoomID), |
145 |
8 |
{ok, FullOpts}; |
146 |
|
{selected, []} -> |
147 |
42 |
{error, room_not_found} |
148 |
|
end. |
149 |
|
|
150 |
|
-spec forget_room(mongooseim:host_type(), muc_host(), mod_muc:room()) -> |
151 |
|
ok | {error, term()}. |
152 |
|
forget_room(HostType, MucHost, RoomName) -> |
153 |
242 |
F = fun() -> forget_room_transaction(HostType, MucHost, RoomName) end, |
154 |
242 |
{atomic, _Res} = mongoose_rdbms:sql_transaction(HostType, F), |
155 |
242 |
ok. |
156 |
|
|
157 |
|
%% Room helper functions |
158 |
|
|
159 |
|
-spec get_rooms(mongooseim:host_type(), muc_host()) -> {ok, [#muc_room{}]}. |
160 |
|
get_rooms(HostType, MucHost) -> |
161 |
36 |
{selected, RoomRows} = execute_select_rooms(HostType, MucHost), |
162 |
36 |
RoomRecs = [handle_room_row(HostType, MucHost, Row) || Row <- RoomRows], |
163 |
36 |
{ok, RoomRecs}. |
164 |
|
|
165 |
|
handle_room_row(HostType, MucHost, {ExtRoomID, RoomName, ExtOpts}) -> |
166 |
338 |
RoomID = mongoose_rdbms:result_to_integer(ExtRoomID), |
167 |
338 |
FullOpts = get_full_options(HostType, ExtOpts, RoomID), |
168 |
338 |
#muc_room{name_host = {RoomName, MucHost}, opts = FullOpts}. |
169 |
|
|
170 |
|
get_full_options(HostType, ExtOpts, RoomID) -> |
171 |
346 |
{selected, Affs} = execute_select_aff(HostType, RoomID), |
172 |
346 |
decode_opts(ExtOpts, Affs). |
173 |
|
|
174 |
|
%% Nick API functions |
175 |
|
|
176 |
|
-spec can_use_nick(mongooseim:host_type(), muc_host(), client_jid(), mod_muc:nick()) -> boolean(). |
177 |
|
can_use_nick(HostType, MucHost, Jid, Nick) -> |
178 |
619 |
{UserU, UserS} = jid:to_lus(Jid), |
179 |
619 |
case execute_select_nick_user(HostType, MucHost, UserS, Nick) of |
180 |
618 |
{selected, []} -> true; |
181 |
1 |
{selected, [{U}]} -> U == UserU |
182 |
|
end. |
183 |
|
|
184 |
|
%% Get nick associated with jid client_jid() across muc_host() domain |
185 |
|
-spec get_nick(mongooseim:host_type(), muc_host(), client_jid()) -> |
186 |
|
{ok, mod_muc:nick()} | {error, not_registered}. |
187 |
|
get_nick(HostType, MucHost, Jid) -> |
188 |
13 |
{UserU, UserS} = jid:to_lus(Jid), |
189 |
13 |
case execute_select_nick(HostType, MucHost, UserU, UserS) of |
190 |
5 |
{selected, []} -> {error, not_registered}; |
191 |
8 |
{selected, [{Nick}]} -> {ok, Nick} |
192 |
|
end. |
193 |
|
|
194 |
|
%% Register nick |
195 |
|
-spec set_nick(mongooseim:host_type(), muc_host(), client_jid(), mod_muc:nick()) -> ok | {error, term()}. |
196 |
|
set_nick(HostType, MucHost, Jid, Nick) when is_binary(Nick), Nick =/= <<>> -> |
197 |
8 |
CanUseNick = can_use_nick(HostType, MucHost, Jid, Nick), |
198 |
8 |
store_nick_transaction(HostType, MucHost, Jid, Nick, CanUseNick). |
199 |
|
|
200 |
|
%% Unregister nick |
201 |
|
%% Unregistered nicks can be used by someone else |
202 |
|
-spec unset_nick(mongooseim:host_type(), muc_host(), client_jid()) -> ok. |
203 |
|
unset_nick(HostType, MucHost, Jid) -> |
204 |
3 |
{UserU, UserS} = jid:to_lus(Jid), |
205 |
3 |
execute_delete_nick(HostType, MucHost, UserU, UserS), |
206 |
3 |
ok. |
207 |
|
|
208 |
|
%% Transaction body functions |
209 |
|
|
210 |
|
store_nick_transaction(_HostType, _MucHost, _Jid, _Nick, false) -> |
211 |
:-( |
{error, conflict}; |
212 |
|
store_nick_transaction(HostType, MucHost, Jid, Nick, true) -> |
213 |
8 |
{LU, LS} = jid:to_lus(Jid), |
214 |
8 |
InsertParams = [MucHost, LU, LS, Nick], |
215 |
8 |
UpdateParams = [Nick], |
216 |
8 |
UniqueKeyValues = [MucHost, LU, LS], |
217 |
8 |
case rdbms_queries:execute_upsert(HostType, muc_nick_upsert, |
218 |
|
InsertParams, UpdateParams, UniqueKeyValues) of |
219 |
8 |
{updated, _} -> ok; |
220 |
:-( |
Error -> Error |
221 |
|
end. |
222 |
|
|
223 |
|
store_room_transaction(HostType, MucHost, RoomName, ExtOpts, Affs) -> |
224 |
305 |
execute_insert_room(HostType, MucHost, RoomName, ExtOpts), |
225 |
305 |
Result = execute_select_room_id(HostType, MucHost, RoomName), |
226 |
305 |
RoomID = mongoose_rdbms:selected_to_integer(Result), |
227 |
305 |
store_aff(HostType, RoomID, Affs), |
228 |
305 |
ok. |
229 |
|
|
230 |
|
store_aff(_HostType, _, undefined) -> |
231 |
3 |
ok; |
232 |
|
store_aff(HostType, RoomID, Affs) -> |
233 |
302 |
F = fun({{UserU, UserS, Resource}, Aff}) -> |
234 |
330 |
ExtAff = aff_atom2db(Aff), |
235 |
330 |
execute_insert_aff(HostType, RoomID, UserU, UserS, Resource, ExtAff) |
236 |
|
end, |
237 |
302 |
lists:foreach(F, Affs). |
238 |
|
|
239 |
|
forget_room_transaction(HostType, MucHost, RoomName) -> |
240 |
547 |
case execute_select_room_id(HostType, MucHost, RoomName) of |
241 |
|
{selected, [{ExtRoomID}]} -> |
242 |
304 |
RoomID = mongoose_rdbms:result_to_integer(ExtRoomID), |
243 |
304 |
execute_delete_affs(HostType, RoomID), |
244 |
304 |
execute_delete_room(HostType, MucHost, RoomName), |
245 |
304 |
ok; |
246 |
|
{selected, []} -> |
247 |
243 |
{error, not_exists} |
248 |
|
end. |
249 |
|
|
250 |
|
%% Execute call functions |
251 |
|
|
252 |
|
-spec execute_insert_room(mongooseim:host_type(), muc_host(), jid:luser(), room_opts()) -> |
253 |
|
updated_result(). |
254 |
|
execute_insert_room(HostType, MucHost, RoomName, ExtOpts) -> |
255 |
305 |
Args = [MucHost, RoomName, ExtOpts], |
256 |
305 |
execute_successfully(HostType, muc_insert_room, Args). |
257 |
|
|
258 |
|
-spec execute_insert_aff(mongooseim:host_type(), RoomID :: room_id(), |
259 |
|
UserU :: jid:luser(), UserS :: jid:lserver(), |
260 |
|
Res ::iolist(), ExtAff :: pos_integer()) -> updated_result(). |
261 |
|
execute_insert_aff(HostType, RoomID, UserU, UserS, Res, ExtAff) -> |
262 |
330 |
Args = [RoomID, UserU, UserS, Res, ExtAff], |
263 |
330 |
execute_successfully(HostType, muc_insert_aff, Args). |
264 |
|
|
265 |
|
-spec execute_select_aff(mongooseim:host_type(), room_id()) -> term(). |
266 |
|
execute_select_aff(HostType, RoomID) -> |
267 |
346 |
execute_successfully(HostType, muc_select_aff, [RoomID]). |
268 |
|
|
269 |
|
-spec execute_select_room_id(mongooseim:host_type(), muc_host(), jid:luser()) -> term(). |
270 |
|
execute_select_room_id(HostType, MucHost, RoomName) -> |
271 |
852 |
execute_successfully(HostType, muc_select_room_id, [MucHost, RoomName]). |
272 |
|
|
273 |
|
-spec execute_select_room(mongooseim:host_type(), muc_host(), jid:luser()) -> term(). |
274 |
|
execute_select_room(HostType, MucHost, RoomName) -> |
275 |
50 |
execute_successfully(HostType, muc_select_room, [MucHost, RoomName]). |
276 |
|
|
277 |
|
-spec execute_delete_affs(mongooseim:host_type(), room_id()) -> term(). |
278 |
|
execute_delete_affs(HostType, RoomID) -> |
279 |
304 |
execute_successfully(HostType, muc_delete_aff, [RoomID]). |
280 |
|
|
281 |
|
-spec execute_delete_room(mongooseim:host_type(), muc_host(), jid:luser()) -> term(). |
282 |
|
execute_delete_room(HostType, MucHost, RoomName) -> |
283 |
304 |
execute_successfully(HostType, muc_delete_room, [MucHost, RoomName]). |
284 |
|
|
285 |
|
-spec execute_select_rooms(mongooseim:host_type(), muc_host()) -> term(). |
286 |
|
execute_select_rooms(HostType, MucHost) -> |
287 |
36 |
execute_successfully(HostType, muc_select_rooms, [MucHost]). |
288 |
|
|
289 |
|
-spec execute_select_nick_user(mongooseim:host_type(), muc_host(), jid:luser(), mod_muc:nick()) -> term(). |
290 |
|
execute_select_nick_user(HostType, MucHost, UserS, Nick) -> |
291 |
619 |
execute_successfully(HostType, muc_select_nick_user, [MucHost, UserS, Nick]). |
292 |
|
|
293 |
|
-spec execute_select_nick(mongooseim:host_type(), muc_host(), jid:luser(), jid:lserver()) -> term(). |
294 |
|
execute_select_nick(HostType, MucHost, UserU, UserS) -> |
295 |
13 |
execute_successfully(HostType, muc_select_nick, [MucHost, UserS, UserU]). |
296 |
|
|
297 |
|
-spec execute_delete_nick(mongooseim:host_type(), muc_host(), jid:luser(), jid:lserver()) -> term(). |
298 |
|
execute_delete_nick(HostType, MucHost, UserU, UserS) -> |
299 |
3 |
execute_successfully(HostType, muc_delete_nick, [MucHost, UserS, UserU]). |
300 |
|
|
301 |
|
%% Conversion functions |
302 |
|
|
303 |
|
-spec aff_atom2db(aff()) -> pos_integer(). |
304 |
300 |
aff_atom2db(owner) -> 1; |
305 |
6 |
aff_atom2db({owner, _}) -> 1; |
306 |
1 |
aff_atom2db(member) -> 2; |
307 |
12 |
aff_atom2db({member, _}) -> 2; |
308 |
:-( |
aff_atom2db(admin) -> 3; |
309 |
6 |
aff_atom2db({admin, _}) -> 3; |
310 |
:-( |
aff_atom2db(outcast) -> 4; |
311 |
5 |
aff_atom2db({outcast, _}) -> 4; |
312 |
:-( |
aff_atom2db(_Other) -> 5. |
313 |
|
|
314 |
|
-spec aff_db2atom(pos_integer()) -> aff(). |
315 |
339 |
aff_db2atom(1) -> owner; |
316 |
10 |
aff_db2atom(2) -> member; |
317 |
4 |
aff_db2atom(3) -> admin; |
318 |
6 |
aff_db2atom(4) -> outcast; |
319 |
:-( |
aff_db2atom(5) -> none. |
320 |
|
|
321 |
|
decode_opts(ExtOpts, Affs) -> |
322 |
346 |
{Opts} = jiffy:decode(ExtOpts), |
323 |
346 |
[{affiliations, decode_affs(Affs)} | keys_as_atoms(Opts)]. |
324 |
|
|
325 |
|
decode_affs(Affs) -> |
326 |
346 |
[{{UserU, UserS, Res}, aff_db2atom(mongoose_rdbms:result_to_integer(Aff))} |
327 |
346 |
|| {UserU, UserS, Res, Aff} <- Affs]. |
328 |
|
|
329 |
|
keys_as_atoms(KVs) -> |
330 |
346 |
[{binary_to_existing_atom(Key, utf8), Value} |
331 |
346 |
|| {Key, Value} <- KVs]. |