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