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