./ct_report/coverage/mod_muc_light_api.COVER.html

1 %% @doc Provide an interface for frontends (like graphql or ctl) to manage MUC Light rooms.
2 -module(mod_muc_light_api).
3
4 -export([create_room/4,
5 create_room/5,
6 invite_to_room/3,
7 change_room_config/4,
8 change_affiliation/4,
9 remove_user_from_room/3,
10 send_message/3,
11 delete_room/2,
12 delete_room/1,
13 get_room_messages/3,
14 get_room_messages/4,
15 get_room_messages/5,
16 get_user_rooms/1,
17 get_room_info/1,
18 get_room_info/2,
19 get_room_aff/1,
20 get_room_aff/2,
21 get_blocking_list/1,
22 set_blocking/2
23 ]).
24
25 -include("mod_muc_light.hrl").
26 -include("mongoose.hrl").
27 -include("jlib.hrl").
28 -include("mongoose_rsm.hrl").
29
30 -type create_room_result() :: {ok, room()} | {already_exists | max_occupants_reached |
31 validation_error | muc_server_not_found, iolist()}.
32
33 -type room() :: #{jid := jid:jid(),
34 name := binary(),
35 subject := binary(),
36 aff_users := aff_users()}.
37
38 -export_type([room/0]).
39
40 -define(ROOM_DELETED_SUCC_RESULT, {ok, "Room deleted successfully"}).
41 -define(USER_NOT_ROOM_MEMBER_RESULT, {not_room_member, "Given user does not occupy this room"}).
42 -define(ROOM_NOT_FOUND_RESULT, {room_not_found, "Room not found"}).
43 -define(DELETE_NOT_EXISTING_ROOM_RESULT, {room_not_found, "Cannot remove not existing room"}).
44 -define(MUC_SERVER_NOT_FOUND_RESULT, {muc_server_not_found, "MUC Light server not found"}).
45 -define(VALIDATION_ERROR_RESULT(Key, Reason),
46 {validation_error, io_lib:format("Validation failed for key: ~p with reason ~p",
47 [Key, Reason])}).
48
49 -spec create_room(jid:lserver(), jid:jid(), binary(), binary()) -> create_room_result().
50 create_room(MUCLightDomain, CreatorJID, RoomTitle, Subject) ->
51 60 RoomJID = jid:make_bare(<<>>, MUCLightDomain),
52 60 create_room_raw(RoomJID, CreatorJID, RoomTitle, Subject).
53
54 -spec create_room(jid:lserver(), jid:luser(), jid:jid(), binary(), binary()) ->
55 create_room_result().
56 create_room(MUCLightDomain, RoomID, CreatorJID, RoomTitle, Subject) ->
57 13 RoomJID = jid:make_bare(RoomID, MUCLightDomain),
58 13 create_room_raw(RoomJID, CreatorJID, RoomTitle, Subject).
59
60 -spec invite_to_room(jid:jid(), jid:jid(), jid:jid()) ->
61 {ok | not_room_member | muc_server_not_found, iolist()}.
62 invite_to_room(#jid{lserver = MUCServer} = RoomJID, SenderJID, RecipientJID) ->
63 20 case mongoose_domain_api:get_subdomain_host_type(MUCServer) of
64 {ok, HostType} ->
65 18 RecipientBin = jid:to_binary(jid:to_bare(RecipientJID)),
66 18 case is_user_room_member(HostType, jid:to_lus(SenderJID), jid:to_lus(RoomJID)) of
67 true ->
68 14 S = jid:to_bare(SenderJID),
69 14 R = jid:to_bare(RoomJID),
70 14 Changes = query(?NS_MUC_LIGHT_AFFILIATIONS,
71 [affiliate(RecipientBin, <<"member">>)]),
72 14 ejabberd_router:route(S, R, iq(jid:to_binary(S), jid:to_binary(R),
73 <<"set">>, [Changes])),
74 14 {ok, "User invited successfully"};
75 false ->
76 4 ?USER_NOT_ROOM_MEMBER_RESULT
77 end;
78 {error, not_found} ->
79 2 ?MUC_SERVER_NOT_FOUND_RESULT
80 end.
81
82
83 -spec change_room_config(jid:jid(), jid:jid(), binary(), binary()) ->
84 {ok, room()} | {not_room_member | not_allowed | room_not_found |
85 validation_error | muc_server_not_found, iolist()}.
86 change_room_config(#jid{luser = RoomID, lserver = MUCServer} = RoomJID,
87 UserJID, RoomName, Subject) ->
88 13 case mongoose_domain_api:get_subdomain_info(MUCServer) of
89 {ok, #{host_type := HostType, parent_domain := LServer}} ->
90 11 UserUS = jid:to_bare(UserJID),
91 11 ConfigReq = #config{ raw_config =
92 [{<<"roomname">>, RoomName}, {<<"subject">>, Subject}]},
93 11 Acc = mongoose_acc:new(#{location => ?LOCATION, lserver => LServer,
94 host_type => HostType}),
95 11 case mod_muc_light:change_room_config(UserUS, RoomID, MUCServer, ConfigReq, Acc) of
96 {ok, RoomJID, _} ->
97 4 {ok, make_room(RoomJID, RoomName, Subject, [])};
98 {error, item_not_found} ->
99 2 ?USER_NOT_ROOM_MEMBER_RESULT;
100 {error, not_allowed} ->
101 3 {not_allowed, "Given user does not have permission to change config"};
102 {error, not_exists} ->
103 2 ?ROOM_NOT_FOUND_RESULT;
104 {error, {error, {Key, Reason}}} ->
105
:-(
?VALIDATION_ERROR_RESULT(Key, Reason)
106 end;
107 {error, not_found} ->
108 2 ?MUC_SERVER_NOT_FOUND_RESULT
109 end.
110
111 -spec change_affiliation(jid:jid(), jid:jid(), jid:jid(), binary()) -> ok.
112 change_affiliation(RoomJID, SenderJID, RecipientJID, Affiliation) ->
113 23 RecipientBare = jid:to_bare(RecipientJID),
114 23 S = jid:to_bare(SenderJID),
115 23 Changes = query(?NS_MUC_LIGHT_AFFILIATIONS,
116 [affiliate(jid:to_binary(RecipientBare), Affiliation)]),
117 23 ejabberd_router:route(S, RoomJID, iq(jid:to_binary(S), jid:to_binary(RoomJID),
118 <<"set">>, [Changes])),
119 23 ok.
120
121 -spec remove_user_from_room(jid:jid(), jid:jid(), jid:jid()) ->
122 {ok, iolist()}.
123 remove_user_from_room(RoomJID, SenderJID, RecipientJID) ->
124 4 ok = change_affiliation(RoomJID, SenderJID, RecipientJID, <<"none">>),
125 4 {ok, io_lib:format("Stanza kicking user ~s sent successfully", [jid:to_binary(RecipientJID)])}.
126
127 -spec send_message(jid:jid(), jid:jid(), binary()) ->
128 {ok | not_room_member | muc_server_not_found, iolist()}.
129 send_message(#jid{lserver = MUCServer} = RoomJID, SenderJID, Message) ->
130 11 case mongoose_domain_api:get_subdomain_host_type(MUCServer) of
131 {ok, HostType} ->
132 9 Body = #xmlel{name = <<"body">>,
133 children = [ #xmlcdata{ content = Message } ]
134 },
135 9 Stanza = #xmlel{name = <<"message">>,
136 attrs = [{<<"type">>, <<"groupchat">>}],
137 children = [ Body ]
138 },
139 9 SenderBare = jid:to_bare(SenderJID),
140 9 case is_user_room_member(HostType, jid:to_lus(SenderBare), jid:to_lus(RoomJID)) of
141 true ->
142 5 RoomBare = jid:to_bare(RoomJID),
143 5 ejabberd_router:route(SenderBare, RoomBare, Stanza),
144 5 {ok, "Message sent successfully"};
145 false ->
146 4 ?USER_NOT_ROOM_MEMBER_RESULT
147 end;
148 {error, not_found}->
149 2 ?MUC_SERVER_NOT_FOUND_RESULT
150 end.
151
152 -spec delete_room(jid:jid(), jid:jid()) ->
153 {ok | not_allowed | room_not_found | muc_server_not_found , iolist()}.
154 delete_room(#jid{lserver = MUCServer} = RoomJID, UserJID) ->
155 4 case mongoose_domain_api:get_subdomain_host_type(MUCServer) of
156 {ok, HostType} ->
157 3 case get_room_user_aff(HostType, RoomJID, UserJID) of
158 {ok, owner} ->
159 1 ok = mod_muc_light_db_backend:destroy_room(HostType, jid:to_lus(RoomJID)),
160 1 ?ROOM_DELETED_SUCC_RESULT;
161 {ok, none} ->
162
:-(
?USER_NOT_ROOM_MEMBER_RESULT;
163 {ok, member} ->
164 1 {not_allowed, "Given user cannot delete this room"};
165 {error, room_not_found} ->
166 1 ?DELETE_NOT_EXISTING_ROOM_RESULT
167 end;
168 {error, not_found}->
169 1 ?MUC_SERVER_NOT_FOUND_RESULT
170 end.
171
172 -spec delete_room(jid:jid()) -> {ok | room_not_found | muc_server_not_found, iolist()}.
173 delete_room(RoomJID) ->
174 5 try mod_muc_light:delete_room(jid:to_lus(RoomJID)) of
175 ok ->
176 2 ?ROOM_DELETED_SUCC_RESULT;
177 {error, not_exists} ->
178 2 ?DELETE_NOT_EXISTING_ROOM_RESULT
179 catch
180 error:{muc_host_to_host_type_failed, _, _} ->
181 1 ?MUC_SERVER_NOT_FOUND_RESULT
182 end.
183 -spec get_room_messages(jid:jid(), jid:jid(), integer() | undefined,
184 mod_mam:unix_timestamp() | undefined) ->
185 {ok, list()} | {muc_server_not_found | internal | not_room_member, iolist()}.
186 get_room_messages(RoomJID, UserJID, PageSize, Before) ->
187 5 case mongoose_domain_api:get_subdomain_host_type(RoomJID#jid.lserver) of
188 {ok, HostType} ->
189 4 case is_user_room_member(HostType, jid:to_lus(UserJID), jid:to_lus(RoomJID)) of
190 true ->
191 3 get_room_messages(HostType, RoomJID, UserJID, PageSize, Before);
192 false ->
193 1 ?USER_NOT_ROOM_MEMBER_RESULT
194 end;
195 {error, not_found} ->
196 1 ?MUC_SERVER_NOT_FOUND_RESULT
197 end.
198
199 -spec get_room_messages(jid:jid(), integer() | undefined,
200 mod_mam:unix_timestamp() | undefined) ->
201 {ok, [mod_mam:message_row()]} | {muc_server_not_found | internal, iolist()}.
202 get_room_messages(RoomJID, PageSize, Before) ->
203 4 case mongoose_domain_api:get_subdomain_host_type(RoomJID#jid.lserver) of
204 {ok, HostType} ->
205 3 get_room_messages(HostType, RoomJID, undefined, PageSize, Before);
206 {error, not_found} ->
207 1 ?MUC_SERVER_NOT_FOUND_RESULT
208 end.
209
210 -spec get_room_messages(mongooseim:host_type(), jid:jid(), jid:jid() | undefined,
211 integer() | undefined, mod_mam:unix_timestamp() | undefined) ->
212 {ok, list()} | {internal, iolist()}.
213 get_room_messages(HostType, RoomJID, CallerJID, PageSize, Before) ->
214 16 ArchiveID = mod_mam_muc:archive_id_int(HostType, RoomJID),
215 16 Now = os:system_time(microsecond),
216 16 End = maybe_before(Before, Now),
217 16 RSM = #rsm_in{direction = before, id = undefined},
218 16 Params = #{archive_id => ArchiveID,
219 owner_jid => RoomJID,
220 rsm => RSM,
221 borders => undefined,
222 start_ts => undefined,
223 end_ts => End,
224 now => Now,
225 with_jid => undefined,
226 search_text => undefined,
227 page_size => PageSize,
228 limit_passed => true,
229 max_result_limit => 50,
230 is_simple => true},
231 16 case mod_mam_muc:lookup_messages(HostType, maybe_caller_jid(CallerJID, Params)) of
232 {ok, {_, _, Messages}} ->
233 16 {ok, Messages};
234 {error, Term} ->
235
:-(
{internal, io_lib:format("Internal error occured ~p", [Term])}
236 end.
237
238 -spec get_room_info(jid:jid(), jid:jid()) ->
239 {ok, room()} | {muc_server_not_found | room_not_found | not_room_member, iolist()}.
240 get_room_info(RoomJID, UserJID) ->
241 5 case get_room_info(RoomJID) of
242 {ok, #{aff_users := Affs} = Room} ->
243 3 case get_aff(jid:to_lus(UserJID), Affs) of
244 none ->
245 1 ?USER_NOT_ROOM_MEMBER_RESULT;
246 _ ->
247 2 {ok, Room}
248 end;
249 Error ->
250 2 Error
251 end.
252
253 -spec get_room_info(jid:jid()) -> {ok, room()} | {muc_server_not_found | room_not_found, iolist()}.
254 get_room_info(#jid{lserver = MUCServer} = RoomJID) ->
255 8 case mongoose_domain_api:get_subdomain_host_type(MUCServer) of
256 {ok, HostType} ->
257 6 case mod_muc_light_db_backend:get_info(HostType, jid:to_lus(RoomJID)) of
258 {ok, [{roomname, Name}, {subject, Subject}], AffUsers, _Version} ->
259 4 {ok, make_room(jid:to_binary(RoomJID), Name, Subject, AffUsers)};
260 {error, not_exists} ->
261 2 ?ROOM_NOT_FOUND_RESULT
262 end;
263 {error, not_found}->
264 2 ?MUC_SERVER_NOT_FOUND_RESULT
265 end.
266
267 -spec get_room_aff(jid:jid(), jid:jid()) ->
268 {ok, aff_users()} | {muc_server_not_found | room_not_found, iolist()}.
269 get_room_aff(RoomJID, UserJID) ->
270 5 case get_room_aff(RoomJID) of
271 {ok, Affs} ->
272 3 case get_aff(jid:to_lus(UserJID), Affs) of
273 none ->
274 1 ?USER_NOT_ROOM_MEMBER_RESULT;
275 _ ->
276 2 {ok, Affs}
277 end;
278 Error ->
279 2 Error
280 end.
281
282 -spec get_room_aff(jid:jid()) ->
283 {ok, aff_users()} | {muc_server_not_found | room_not_found, iolist()}.
284 get_room_aff(#jid{lserver = MUCServer} = RoomJID) ->
285 8 case mongoose_domain_api:get_subdomain_host_type(MUCServer) of
286 {ok, HostType} ->
287 6 case mod_muc_light_db_backend:get_aff_users(HostType, jid:to_lus(RoomJID)) of
288 {ok, AffUsers, _Version} ->
289 4 {ok, AffUsers};
290 {error, not_exists} ->
291 2 ?ROOM_NOT_FOUND_RESULT
292 end;
293 {error, not_found}->
294 2 ?MUC_SERVER_NOT_FOUND_RESULT
295 end.
296
297 -spec get_user_rooms(jid:jid()) -> {ok, [RoomUS :: jid:simple_bare_jid()]} |
298 {muc_server_not_found, iolist()}.
299 get_user_rooms(#jid{lserver = LServer} = UserJID) ->
300 4 case mongoose_domain_api:get_domain_host_type(LServer) of
301 {ok, HostType} ->
302 3 UserUS = jid:to_lus(UserJID),
303 3 MUCServer = mod_muc_light_utils:server_host_to_muc_host(HostType, LServer),
304 3 {ok, mod_muc_light_db_backend:get_user_rooms(HostType, UserUS, MUCServer)};
305 {error, not_found} ->
306 1 ?MUC_SERVER_NOT_FOUND_RESULT
307 end.
308
309 -spec get_blocking_list(jid:jid()) -> {ok, [blocking_item()]} | {muc_server_not_found, iolist()}.
310 get_blocking_list(#jid{lserver = LServer} = User) ->
311 7 case mongoose_domain_api:get_domain_host_type(LServer) of
312 {ok, HostType} ->
313 6 MUCServer = mod_muc_light_utils:server_host_to_muc_host(HostType, LServer),
314 6 {ok, mod_muc_light_db_backend:get_blocking(HostType, jid:to_lus(User), MUCServer)};
315 {error, not_found} ->
316 1 ?MUC_SERVER_NOT_FOUND_RESULT
317 end.
318
319 -spec set_blocking(jid:jid(), [blocking_item()]) -> {ok | muc_server_not_found, iolist()}.
320 set_blocking(#jid{lserver = LServer} = User, Items) ->
321 5 case mongoose_domain_api:get_domain_host_type(LServer) of
322 {ok, HostType} ->
323 4 MUCServer = mod_muc_light_utils:server_host_to_muc_host(HostType, LServer),
324 4 Q = query(?NS_MUC_LIGHT_BLOCKING, [blocking_item(I) || I <- Items]),
325 4 Iq = iq(jid:to_binary(User), MUCServer, <<"set">>, [Q]),
326 4 ejabberd_router:route(User, jid:from_binary(MUCServer), Iq),
327 4 {ok, "User blocking list updated successfully"};
328 {error, not_found} ->
329 1 ?MUC_SERVER_NOT_FOUND_RESULT
330 end.
331
332 %% Internal
333
334 -spec create_room_raw(jid:jid(), jid:jid(), binary(), binary()) -> create_room_result().
335 create_room_raw(InRoomJID, CreatorJID, RoomTitle, Subject) ->
336 73 Config = make_room_config(RoomTitle, Subject),
337 73 try mod_muc_light:try_to_create_room(CreatorJID, InRoomJID, Config) of
338 {ok, RoomJID, #create{aff_users = AffUsers}} ->
339 65 {ok, make_room(RoomJID, RoomTitle, Subject, AffUsers)};
340 {error, exists} ->
341 3 {already_exist, "Room already exists"};
342 {error, max_occupants_reached} ->
343
:-(
{max_occupants_reached, "Max occupants number reached"};
344 {error, {Key, Reason}} ->
345
:-(
?VALIDATION_ERROR_RESULT(Key, Reason)
346 catch
347 error:{muc_host_to_host_type_failed, _, _} ->
348 5 ?MUC_SERVER_NOT_FOUND_RESULT
349 end.
350
351 -spec blocking_item(blocking_item()) -> exml:element().
352 blocking_item({What, Action, Who}) ->
353 5 #xmlel{name = atom_to_binary(What),
354 attrs = [{<<"action">>, atom_to_binary(Action)}],
355 children = [#xmlcdata{ content = jid:to_binary(Who)}]
356 }.
357
358 -spec make_room_config(binary(), binary()) -> create_req_props().
359 make_room_config(Name, Subject) ->
360 73 #create{raw_config = [{<<"roomname">>, Name},
361 {<<"subject">>, Subject}]
362 }.
363
364 -spec get_room_user_aff(mongooseim:host_type(), jid:jid(), jid:jid()) ->
365 {ok, aff()} | {error, room_not_found}.
366 get_room_user_aff(HostType, RoomJID, UserJID) ->
367 3 RoomUS = jid:to_lus(RoomJID),
368 3 UserUS = jid:to_lus(UserJID),
369 3 case mod_muc_light_db_backend:get_aff_users(HostType, RoomUS) of
370 {ok, Affs, _Version} ->
371 2 {ok, get_aff(UserUS, Affs)};
372 {error, not_exists} ->
373 1 {error, room_not_found}
374 end.
375
376 -spec get_aff(jid:simple_bare_jid(), aff_users()) -> aff().
377 get_aff(UserUS, Affs) ->
378 8 case lists:keyfind(UserUS, 1, Affs) of
379 6 {_, Aff} -> Aff;
380 2 false -> none
381 end.
382
383 -spec is_user_room_member(mongooseim:host_type(), jid:simple_bare_jid(),
384 jid:simple_bare_jid()) -> boolean().
385 is_user_room_member(HostType, UserUS, {_, MUCServer} = RoomLUS) ->
386 31 case mod_muc_light_db_backend:get_user_rooms(HostType, UserUS, MUCServer) of
387 [] ->
388 5 false;
389 RoomJIDs when is_list(RoomJIDs) ->
390 26 lists:any(fun(LUS) -> LUS =:= RoomLUS end, RoomJIDs)
391 end.
392
393 make_room(JID, Name, Subject, AffUsers) ->
394 73 #{jid => JID, name => Name, subject => Subject, aff_users => AffUsers}.
395
396 iq(To, From, Type, Children) ->
397 41 UUID = uuid:uuid_to_string(uuid:get_v4(), binary_standard),
398 41 #xmlel{name = <<"iq">>,
399 attrs = [{<<"from">>, From},
400 {<<"to">>, To},
401 {<<"type">>, Type},
402 {<<"id">>, UUID}],
403 children = Children
404 }.
405
406 query(NS, Children) when is_binary(NS), is_list(Children) ->
407 41 #xmlel{name = <<"query">>,
408 attrs = [{<<"xmlns">>, NS}],
409 children = Children
410 }.
411
412 affiliate(JID, Kind) when is_binary(JID), is_binary(Kind) ->
413 37 #xmlel{name = <<"user">>,
414 attrs = [{<<"affiliation">>, Kind}],
415 children = [ #xmlcdata{ content = JID } ]
416 }.
417
418 maybe_before(undefined, Now) ->
419 10 Now;
420 maybe_before(Timestamp, _) ->
421 6 Timestamp.
422
423 maybe_caller_jid(undefined, Params) ->
424 4 Params;
425 maybe_caller_jid(CallerJID, Params) ->
426 12 Params#{caller_jid => CallerJID}.
Line Hits Source