1 |
|
%%% ==================================================================== |
2 |
|
%%% ``The contents of this file are subject to the Erlang Public License, |
3 |
|
%%% Version 1.1, (the "License"); you may not use this file except in |
4 |
|
%%% compliance with the License. You should have received a copy of the |
5 |
|
%%% Erlang Public License along with this software. If not, it can be |
6 |
|
%%% retrieved via the world wide web at http://www.erlang.org/. |
7 |
|
%%% |
8 |
|
%%% |
9 |
|
%%% Software distributed under the License is distributed on an "AS IS" |
10 |
|
%%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See |
11 |
|
%%% the License for the specific language governing rights and limitations |
12 |
|
%%% under the License. |
13 |
|
%%% |
14 |
|
%%% |
15 |
|
%%% The Initial Developer of the Original Code is ProcessOne. |
16 |
|
%%% Portions created by ProcessOne are Copyright 2006-2015, ProcessOne |
17 |
|
%%% All Rights Reserved.'' |
18 |
|
%%% This software is copyright 2006-2015, ProcessOne. |
19 |
|
%%% |
20 |
|
%%% @copyright 2006-2015 ProcessOne |
21 |
|
%%% @author Christophe Romain <christophe.romain@process-one.net> |
22 |
|
%%% [http://www.process-one.net/] |
23 |
|
%%% @end |
24 |
|
%%% ==================================================================== |
25 |
|
|
26 |
|
%%% @doc The module <strong>{@module}</strong> is the default PubSub plugin. |
27 |
|
%%% <p>It is used as a default for all unknown PubSub node type. It can serve |
28 |
|
%%% as a developer basis and reference to build its own custom pubsub node |
29 |
|
%%% types.</p> |
30 |
|
%%% <p>PubSub plugin nodes are using the {@link gen_node} behaviour.</p> |
31 |
|
|
32 |
|
-module(node_flat). |
33 |
|
-behaviour(gen_pubsub_node). |
34 |
|
-author('christophe.romain@process-one.net'). |
35 |
|
|
36 |
|
-include("pubsub.hrl"). |
37 |
|
-include("jlib.hrl"). |
38 |
|
-include("mongoose.hrl"). |
39 |
|
|
40 |
|
-export([based_on/0, init/3, terminate/2, options/0, features/0, |
41 |
|
create_node_permission/6, create_node/2, delete_node/1, |
42 |
|
purge_node/2, subscribe_node/8, unsubscribe_node/4, |
43 |
|
publish_item/9, delete_item/4, remove_extra_items/3, |
44 |
|
get_entity_affiliations/2, get_node_affiliations/1, |
45 |
|
get_affiliation/2, set_affiliation/3, |
46 |
|
get_entity_subscriptions/2, get_node_subscriptions/1, |
47 |
|
get_subscriptions/2, set_subscriptions/4, |
48 |
|
get_pending_nodes/2, |
49 |
|
get_items_if_authorised/3, get_items/3, get_item/7, |
50 |
|
get_item/2, set_item/1, get_item_name/3, node_to_path/1, |
51 |
|
path_to_node/1, can_fetch_item/2, is_subscribed/1, |
52 |
|
should_delete_when_owner_removed/0, remove_user/2]). |
53 |
|
|
54 |
|
-define(MOD_PUBSUB_DB_BACKEND, mod_pubsub_db_backend). |
55 |
|
-ignore_xref([ |
56 |
|
{?MOD_PUBSUB_DB_BACKEND, create_node, 2}, |
57 |
|
{?MOD_PUBSUB_DB_BACKEND, del_item, 2}, |
58 |
|
{?MOD_PUBSUB_DB_BACKEND, get_states, 2}, |
59 |
|
{?MOD_PUBSUB_DB_BACKEND, remove_items, 3}, |
60 |
|
{?MOD_PUBSUB_DB_BACKEND, get_state, 2}, |
61 |
|
{?MOD_PUBSUB_DB_BACKEND, del_node, 1}, |
62 |
|
{?MOD_PUBSUB_DB_BACKEND, get_affiliation, 2}, |
63 |
|
{?MOD_PUBSUB_DB_BACKEND, get_states_by_bare, 1}, |
64 |
|
{?MOD_PUBSUB_DB_BACKEND, get_states_by_bare_and_full, 1}, |
65 |
|
{?MOD_PUBSUB_DB_BACKEND, get_item, 2}, |
66 |
|
{?MOD_PUBSUB_DB_BACKEND, get_node_entity_subscriptions, 2}, |
67 |
|
{?MOD_PUBSUB_DB_BACKEND, delete_subscription, 2}, |
68 |
|
{?MOD_PUBSUB_DB_BACKEND, add_subscription, 5}, |
69 |
|
{?MOD_PUBSUB_DB_BACKEND, del_items, 2}, |
70 |
|
{?MOD_PUBSUB_DB_BACKEND, get_states, 1}, |
71 |
|
{?MOD_PUBSUB_DB_BACKEND, get_states_by_lus, 1}, |
72 |
|
{?MOD_PUBSUB_DB_BACKEND, get_items, 2}, |
73 |
|
{?MOD_PUBSUB_DB_BACKEND, delete_subscription, 3}, |
74 |
|
{?MOD_PUBSUB_DB_BACKEND, delete_all_subscriptions, 2}, |
75 |
|
{?MOD_PUBSUB_DB_BACKEND, set_item, 1}, |
76 |
|
{?MOD_PUBSUB_DB_BACKEND, set_affiliation, 3}, |
77 |
|
{?MOD_PUBSUB_DB_BACKEND, delete_node, 1}, |
78 |
|
{?MOD_PUBSUB_DB_BACKEND, find_nodes_by_affiliated_user, 1}, |
79 |
|
{?MOD_PUBSUB_DB_BACKEND, dirty, 2}, |
80 |
|
{?MOD_PUBSUB_DB_BACKEND, delete_user_subscriptions, 1}, |
81 |
|
{?MOD_PUBSUB_DB_BACKEND, remove_all_items, 1}, |
82 |
|
{?MOD_PUBSUB_DB_BACKEND, get_node_subscriptions, 1}, |
83 |
|
{?MOD_PUBSUB_DB_BACKEND, get_idxs_of_own_nodes_with_pending_subs, 1}, |
84 |
|
{?MOD_PUBSUB_DB_BACKEND, add_item, 3}, |
85 |
|
{?MOD_PUBSUB_DB_BACKEND, update_subscription, 4}, |
86 |
|
can_fetch_item/2, is_subscribed/1, set_subscriptions/4 |
87 |
|
]). |
88 |
|
|
89 |
:-( |
based_on() -> none. |
90 |
|
|
91 |
|
init(_Host, _ServerHost, _Opts) -> |
92 |
58 |
ok. |
93 |
|
|
94 |
|
terminate(_Host, _ServerHost) -> |
95 |
58 |
ok. |
96 |
|
|
97 |
|
options() -> |
98 |
163 |
[{deliver_payloads, true}, |
99 |
|
{notify_config, false}, |
100 |
|
{notify_delete, false}, |
101 |
|
{notify_retract, false}, |
102 |
|
{purge_offline, false}, |
103 |
|
{persist_items, true}, |
104 |
|
{max_items, ?MAXITEMS}, |
105 |
|
{subscribe, true}, |
106 |
|
{access_model, open}, |
107 |
|
{roster_groups_allowed, []}, |
108 |
|
{publish_model, publishers}, |
109 |
|
{notification_type, headline}, |
110 |
|
{max_payload_size, ?MAX_PAYLOAD_SIZE}, |
111 |
|
{send_last_published_item, never}, |
112 |
|
{deliver_notifications, true}, |
113 |
|
{presence_based_delivery, false}]. |
114 |
|
|
115 |
|
features() -> |
116 |
787 |
[<<"create-nodes">>, |
117 |
|
<<"auto-create">>, |
118 |
|
<<"access-authorize">>, |
119 |
|
<<"delete-nodes">>, |
120 |
|
<<"delete-items">>, |
121 |
|
<<"get-pending">>, |
122 |
|
<<"instant-nodes">>, |
123 |
|
<<"manage-subscriptions">>, |
124 |
|
<<"modify-affiliations">>, |
125 |
|
<<"outcast-affiliation">>, |
126 |
|
<<"persistent-items">>, |
127 |
|
<<"publish">>, |
128 |
|
<<"publish-only-affiliation">>, |
129 |
|
<<"purge-nodes">>, |
130 |
|
<<"retract-items">>, |
131 |
|
<<"retrieve-affiliations">>, |
132 |
|
<<"retrieve-items">>, |
133 |
|
<<"retrieve-subscriptions">>, |
134 |
|
<<"subscribe">>, |
135 |
|
<<"subscription-notifications">>, |
136 |
|
<<"subscription-options">> |
137 |
|
]. |
138 |
|
|
139 |
|
|
140 |
|
%% @doc Checks if the current user has the permission to create the requested node |
141 |
|
%% <p>In flat node, any unused node name is allowed. The access parameter is also |
142 |
|
%% checked. This parameter depends on the value of the |
143 |
|
%% <tt>access_createnode</tt> ACL value in ejabberd config file.</p> |
144 |
|
create_node_permission(Host, ServerHost, _Node, _ParentNode, Owner, Access) -> |
145 |
115 |
Allowed = case jid:to_lower(Owner) of |
146 |
|
{<<"">>, Host, <<"">>} -> |
147 |
:-( |
true; % pubsub service always allowed |
148 |
|
_ -> |
149 |
115 |
{ok, HostType} = mongoose_domain_api:get_domain_host_type(ServerHost), |
150 |
115 |
acl:match_rule(HostType, ServerHost, Access, Owner) =:= allow |
151 |
|
end, |
152 |
115 |
{result, Allowed}. |
153 |
|
|
154 |
|
create_node(Nidx, Owner) -> |
155 |
229 |
mod_pubsub_db_backend:create_node(Nidx, jid:to_lower(Owner)), |
156 |
229 |
{result, {default, broadcast}}. |
157 |
|
|
158 |
|
delete_node(Nodes) -> |
159 |
151 |
Tr = fun (#pubsub_state{stateid = {J, _}, subscriptions = Ss}) -> |
160 |
239 |
lists:map(fun (S) -> {J, S} end, Ss) |
161 |
|
end, |
162 |
151 |
Reply = lists:map(fun (#pubsub_node{id = Nidx} = PubsubNode) -> |
163 |
152 |
{ok, States} = mod_pubsub_db_backend:del_node(Nidx), |
164 |
152 |
{PubsubNode, lists:flatmap(Tr, States)} |
165 |
|
end, Nodes), |
166 |
151 |
{result, {default, broadcast, Reply}}. |
167 |
|
|
168 |
|
%% @doc <p>Accepts or rejects subcription requests on a PubSub node.</p> |
169 |
|
%% <p>The mechanism works as follow: |
170 |
|
%% <ul> |
171 |
|
%% <li>The main PubSub module prepares the subscription and passes the |
172 |
|
%% result of the preparation as a record.</li> |
173 |
|
%% <li>This function gets the prepared record and several other parameters and |
174 |
|
%% can decide to:<ul> |
175 |
|
%% <li>reject the subscription;</li> |
176 |
|
%% <li>allow it as is, letting the main module perform the database |
177 |
|
%% persistance;</li> |
178 |
|
%% <li>allow it, modifying the record. The main module will store the |
179 |
|
%% modified record;</li> |
180 |
|
%% <li>allow it, but perform the needed persistance operations.</li></ul> |
181 |
|
%% </li></ul></p> |
182 |
|
%% <p>The selected behaviour depends on the return parameter: |
183 |
|
%% <ul> |
184 |
|
%% <li><tt>{error, Reason}</tt>: an IQ error result will be returned. No |
185 |
|
%% subscription will actually be performed.</li> |
186 |
|
%% <li><tt>true</tt>: Subscribe operation is allowed, based on the |
187 |
|
%% unmodified record passed in parameter <tt>SubscribeResult</tt>. If this |
188 |
|
%% parameter contains an error, no subscription will be performed.</li> |
189 |
|
%% <li><tt>{true, PubsubState}</tt>: Subscribe operation is allowed, but |
190 |
|
%% the {@link mod_pubsub:pubsubState()} record returned replaces the value |
191 |
|
%% passed in parameter <tt>SubscribeResult</tt>.</li> |
192 |
|
%% <li><tt>{true, done}</tt>: Subscribe operation is allowed, but the |
193 |
|
%% {@link mod_pubsub:pubsubState()} will be considered as already stored and |
194 |
|
%% no further persistance operation will be performed. This case is used, |
195 |
|
%% when the plugin module is doing the persistance by itself or when it want |
196 |
|
%% to completly disable persistance.</li></ul> |
197 |
|
%% </p> |
198 |
|
%% <p>In the default plugin module, the record is unchanged.</p> |
199 |
|
subscribe_node(Nidx, Sender, Subscriber, AccessModel, |
200 |
|
SendLast, PresenceSubscription, RosterGroup, Options) -> |
201 |
90 |
SenderMatchesSubscriber = jid:are_bare_equal(Sender, Subscriber), |
202 |
90 |
{ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, Subscriber), |
203 |
90 |
{ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, Subscriber), |
204 |
90 |
Whitelisted = lists:member(Affiliation, [member, publisher, owner]), |
205 |
90 |
PendingSubscription = lists:any(fun |
206 |
:-( |
({pending, _, _}) -> true; |
207 |
:-( |
(_) -> false |
208 |
|
end, |
209 |
|
Subscriptions), |
210 |
90 |
case authorize_subscription(SenderMatchesSubscriber, Affiliation, PendingSubscription, |
211 |
|
AccessModel, PresenceSubscription, RosterGroup, Whitelisted) of |
212 |
|
ok -> |
213 |
88 |
{NewSub, SubId} = case Subscriptions of |
214 |
|
[{subscribed, Id, _}|_] -> |
215 |
:-( |
{subscribed, Id}; |
216 |
|
[] -> |
217 |
88 |
Id = make_subid(), |
218 |
88 |
Sub = access_model_to_subscription(AccessModel), |
219 |
88 |
mod_pubsub_db_backend:add_subscription(Nidx, Subscriber, Sub, Id, Options), |
220 |
88 |
{Sub, Id} |
221 |
|
end, |
222 |
88 |
case {NewSub, SendLast} of |
223 |
|
{subscribed, never} -> |
224 |
71 |
{result, {default, subscribed, SubId}}; |
225 |
|
{subscribed, _} -> |
226 |
8 |
{result, {default, subscribed, SubId, send_last}}; |
227 |
|
{_, _} -> |
228 |
9 |
{result, {default, pending, SubId}} |
229 |
|
end; |
230 |
|
{error, _} = Err -> |
231 |
2 |
Err |
232 |
|
end. |
233 |
|
|
234 |
|
-spec access_model_to_subscription(accessModel()) -> pending | subscribed. |
235 |
9 |
access_model_to_subscription(authorize) -> pending; |
236 |
79 |
access_model_to_subscription(_) -> subscribed. |
237 |
|
|
238 |
|
-spec authorize_subscription(SenderMatchesSubscriber :: boolean(), |
239 |
|
Affiliation :: affiliation(), |
240 |
|
PendingSubscription :: boolean(), |
241 |
|
AccessModel :: accessModel(), |
242 |
|
PresenceSubscription :: boolean(), |
243 |
|
RosterGroup :: boolean(), |
244 |
|
Whitelisted :: boolean()) -> ok | {error, exml:element()}. |
245 |
|
authorize_subscription(false, _Affiliation, _PendingSubscription, _AccessModel, |
246 |
|
_PresenceSubscription, _RosterGroup, _Whitelisted) -> |
247 |
:-( |
{error, ?ERR_EXTENDED((mongoose_xmpp_errors:bad_request()), <<"invalid-jid">>)}; |
248 |
|
authorize_subscription(_SenderMatchesSubscriber, Affiliation, _PendingSubscription, _AccessModel, |
249 |
|
_PresenceSubscription, _RosterGroup, _Whitelisted) |
250 |
|
when (Affiliation == outcast) or (Affiliation == publish_only) -> |
251 |
:-( |
{error, mongoose_xmpp_errors:forbidden()}; |
252 |
|
authorize_subscription(_SenderMatchesSubscriber, _Affiliation, true, _AccessModel, |
253 |
|
_PresenceSubscription, _RosterGroup, _Whitelisted) -> |
254 |
:-( |
{error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_authorized()), <<"pending-subscription">>)}; |
255 |
|
authorize_subscription(_SenderMatchesSubscriber, Affiliation, _PendingSubscription, presence, |
256 |
|
false, _RosterGroup, _Whitelisted) when Affiliation /= owner -> |
257 |
:-( |
{error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_authorized()), <<"presence-subscription-required">>)}; |
258 |
|
authorize_subscription(_SenderMatchesSubscriber, Affiliation, _PendingSubscription, roster, |
259 |
|
_PresenceSubscription, false, _Whitelisted) when Affiliation /= owner -> |
260 |
:-( |
{error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_authorized()), <<"not-in-roster-group">>)}; |
261 |
|
authorize_subscription(_SenderMatchesSubscriber, Affiliation, _PendingSubscription, whitelist, |
262 |
|
_PresenceSubscription, _RosterGroup, false) when Affiliation /= owner -> |
263 |
2 |
{error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_allowed()), <<"closed-node">>)}; |
264 |
|
authorize_subscription(_SenderMatchesSubscriber, _Affiliation, _PendingSubscription, _AccessModel, |
265 |
|
_PresenceSubscription, _RosterGroup, _Whitelisted) -> |
266 |
88 |
ok. |
267 |
|
|
268 |
|
%% @doc <p>Unsubscribe the <tt>Subscriber</tt> from the <tt>Node</tt>.</p> |
269 |
|
unsubscribe_node(Nidx, Sender, Subscriber, SubId) -> |
270 |
7 |
SenderMatchesSubscriber = jid:are_bare_equal(Subscriber, Sender), |
271 |
7 |
{ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, Subscriber), |
272 |
7 |
SubIdExists = case SubId of |
273 |
5 |
<<>> -> false; |
274 |
:-( |
Binary when is_binary(Binary) -> true; |
275 |
2 |
_ -> false |
276 |
|
end, |
277 |
7 |
case authenticate_unsubscribe(SenderMatchesSubscriber, Subscriptions, SubIdExists, SubId) of |
278 |
|
sub_id_exists -> |
279 |
:-( |
case lists:keyfind(SubId, 2, Subscriptions) of |
280 |
|
false -> |
281 |
:-( |
{error, |
282 |
|
?ERR_EXTENDED((mongoose_xmpp_errors:unexpected_request_cancel()), |
283 |
|
<<"not-subscribed">>)}; |
284 |
|
_S -> |
285 |
:-( |
mod_pubsub_db_backend:delete_subscription(Nidx, Subscriber, SubId), |
286 |
:-( |
{result, default} |
287 |
|
end; |
288 |
|
remove_all_subs -> |
289 |
2 |
mod_pubsub_db_backend:delete_all_subscriptions(Nidx, Subscriber), |
290 |
2 |
{result, default}; |
291 |
|
remove_only_sub -> |
292 |
5 |
mod_pubsub_db_backend:delete_all_subscriptions(Nidx, Subscriber), |
293 |
5 |
{result, default} |
294 |
|
end. |
295 |
|
|
296 |
|
authenticate_unsubscribe(false, _Subscriptions, _SubIdExists, _SubId) -> |
297 |
:-( |
{error, mongoose_xmpp_errors:forbidden()}; |
298 |
|
authenticate_unsubscribe(_SenderMatchesSubscriber, [], _SubIdExists, _SubId) -> |
299 |
|
%% Requesting entity is not a subscriber |
300 |
:-( |
{error, ?ERR_EXTENDED((mongoose_xmpp_errors:unexpected_request_cancel()), <<"not-subscribed">>)}; |
301 |
|
authenticate_unsubscribe(_SenderMatchesSubscriber, _Subscriptions, true, _SubId) -> |
302 |
|
%% Subid supplied, so use that. |
303 |
:-( |
sub_id_exists; |
304 |
|
authenticate_unsubscribe(_SenderMatchesSubscriber, _Subscriptions, _SubIdExists, all) -> |
305 |
|
%% Asking to remove all subscriptions to the given node |
306 |
2 |
remove_all_subs; |
307 |
|
authenticate_unsubscribe(_SenderMatchesSubscriber, [_], _SubIdExists, _SubId) -> |
308 |
|
%% No subid supplied, but there's only one matching subscription |
309 |
5 |
remove_only_sub; |
310 |
|
authenticate_unsubscribe(_SenderMatchesSubscriber, _Subscriptions, _SubIdExists, _SubId) -> |
311 |
:-( |
{error, ?ERR_EXTENDED((mongoose_xmpp_errors:bad_request()), <<"subid-required">>)}. |
312 |
|
|
313 |
|
%% @doc <p>Publishes the item passed as parameter.</p> |
314 |
|
%% <p>The mechanism works as follow: |
315 |
|
%% <ul> |
316 |
|
%% <li>The main PubSub module prepares the item to publish and passes the |
317 |
|
%% result of the preparation as a {@link mod_pubsub:pubsubItem()} record.</li> |
318 |
|
%% <li>This function gets the prepared record and several other parameters and can decide to:<ul> |
319 |
|
%% <li>reject the publication;</li> |
320 |
|
%% <li>allow the publication as is, letting the main module perform the database persistance;</li> |
321 |
|
%% <li>allow the publication, modifying the record. |
322 |
|
%% The main module will store the modified record;</li> |
323 |
|
%% <li>allow it, but perform the needed persistance operations.</li></ul> |
324 |
|
%% </li></ul></p> |
325 |
|
%% <p>The selected behaviour depends on the return parameter: |
326 |
|
%% <ul> |
327 |
|
%% <li><tt>{error, Reason}</tt>: an iq error result will be return. No |
328 |
|
%% publication is actually performed.</li> |
329 |
|
%% <li><tt>true</tt>: Publication operation is allowed, based on the |
330 |
|
%% unmodified record passed in parameter <tt>Item</tt>. If the <tt>Item</tt> |
331 |
|
%% parameter contains an error, no subscription will actually be |
332 |
|
%% performed.</li> |
333 |
|
%% <li><tt>{true, Item}</tt>: Publication operation is allowed, but the |
334 |
|
%% {@link mod_pubsub:pubsubItem()} record returned replaces the value passed |
335 |
|
%% in parameter <tt>Item</tt>. The persistance will be performed by the main |
336 |
|
%% module.</li> |
337 |
|
%% <li><tt>{true, done}</tt>: Publication operation is allowed, but the |
338 |
|
%% {@link mod_pubsub:pubsubItem()} will be considered as already stored and |
339 |
|
%% no further persistance operation will be performed. This case is used, |
340 |
|
%% when the plugin module is doing the persistance by itself or when it want |
341 |
|
%% to completly disable persistance.</li></ul> |
342 |
|
%% </p> |
343 |
|
%% <p>In the default plugin module, the record is unchanged.</p> |
344 |
|
publish_item(_ServerHost, Nidx, Publisher, PublishModel, MaxItems, ItemId, ItemPublisher, |
345 |
|
Payload, _PublishOptions) -> |
346 |
|
%% vvvvvvvvvvvv |
347 |
128 |
BarePublisher = jid:to_bare(Publisher), |
348 |
128 |
SubKey = jid:to_lower(Publisher), |
349 |
128 |
GenKey = jid:to_lower(BarePublisher), |
350 |
128 |
{ok, GenState} = mod_pubsub_db_backend:get_state(Nidx, GenKey), |
351 |
128 |
SubState = case Publisher#jid.lresource of |
352 |
|
<<>> -> |
353 |
:-( |
GenState; |
354 |
|
_ -> |
355 |
128 |
{ok, SubState0} = mod_pubsub_db_backend:get_state(Nidx, SubKey), |
356 |
128 |
SubState0 |
357 |
|
end, |
358 |
128 |
{ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, GenKey), |
359 |
128 |
Subscribed = case PublishModel of |
360 |
:-( |
subscribers -> is_subscribed(GenState#pubsub_state.subscriptions) orelse |
361 |
:-( |
is_subscribed(SubState#pubsub_state.subscriptions); |
362 |
128 |
_ -> undefined |
363 |
|
end, |
364 |
|
%% ^^^^^^^^^^^^ TODO: Whole this block may be refactored when we migrate pubsub_item |
365 |
|
%% as GenState won't be needed anymore. |
366 |
128 |
Allowed = (PublishModel == open) or |
367 |
|
(PublishModel == publishers) and |
368 |
|
( (Affiliation == owner) or |
369 |
|
(Affiliation == publisher) or |
370 |
|
(Affiliation == publish_only) ) or |
371 |
|
(Subscribed == true), |
372 |
128 |
case Allowed of |
373 |
|
false -> |
374 |
6 |
{error, mongoose_xmpp_errors:forbidden()}; |
375 |
|
true -> |
376 |
122 |
case MaxItems > 0 of |
377 |
|
true -> |
378 |
119 |
Now = os:system_time(microsecond), |
379 |
119 |
Item = make_pubsub_item(Nidx, ItemId, Now, SubKey, GenKey, |
380 |
|
Payload, Publisher, ItemPublisher), |
381 |
119 |
Items = [ItemId | GenState#pubsub_state.items -- [ItemId]], |
382 |
119 |
{result, {_NI, OI}} = remove_extra_items(Nidx, MaxItems, Items), |
383 |
119 |
mod_pubsub_db_backend:add_item(Nidx, GenKey, Item), |
384 |
119 |
mod_pubsub_db_backend:remove_items(Nidx, GenKey, OI), |
385 |
119 |
{result, {default, broadcast, OI}}; |
386 |
|
false -> |
387 |
3 |
{result, {default, broadcast, []}} |
388 |
|
end |
389 |
|
end. |
390 |
|
|
391 |
|
make_pubsub_item(Nidx, ItemId, Now, SubKey, GenKey, Payload, Publisher, ItemPublisher) -> |
392 |
119 |
PubId = {Now, SubKey}, |
393 |
119 |
case get_item(Nidx, ItemId) of |
394 |
|
{result, OldItem} -> |
395 |
2 |
OldItem#pubsub_item{modification = PubId, |
396 |
|
payload = Payload}; |
397 |
|
_ -> |
398 |
117 |
Publisher0 = case ItemPublisher of |
399 |
4 |
true -> Publisher; |
400 |
113 |
false -> undefined |
401 |
|
end, |
402 |
117 |
#pubsub_item{itemid = {ItemId, Nidx}, |
403 |
|
creation = {Now, GenKey}, |
404 |
|
modification = PubId, |
405 |
|
publisher = Publisher0, |
406 |
|
payload = Payload} |
407 |
|
end. |
408 |
|
|
409 |
|
%% @doc <p>This function is used to remove extra items, most notably when the |
410 |
|
%% maximum number of items has been reached.</p> |
411 |
|
%% <p>This function is used internally by the core PubSub module, as no |
412 |
|
%% permission check is performed.</p> |
413 |
|
%% <p>In the default plugin module, the oldest items are removed, but other |
414 |
|
%% rules can be used.</p> |
415 |
|
%% <p>If another PubSub plugin wants to delegate the item removal (and if the |
416 |
|
%% plugin is using the default pubsub storage), it can implements this function like this: |
417 |
|
%% ```remove_extra_items(Nidx, MaxItems, ItemIds) -> |
418 |
|
%% node_default:remove_extra_items(Nidx, MaxItems, ItemIds).'''</p> |
419 |
|
remove_extra_items(_Nidx, unlimited, ItemIds) -> |
420 |
:-( |
{result, {ItemIds, []}}; |
421 |
|
remove_extra_items(Nidx, MaxItems, ItemIds) -> |
422 |
119 |
NewItems = lists:sublist(ItemIds, MaxItems), |
423 |
119 |
OldItems = lists:nthtail(length(NewItems), ItemIds), |
424 |
119 |
del_items(Nidx, OldItems), |
425 |
119 |
{result, {NewItems, OldItems}}. |
426 |
|
|
427 |
|
%% @doc <p>Triggers item deletion.</p> |
428 |
|
%% <p>Default plugin: The user performing the deletion must be the node owner |
429 |
|
%% or a publisher, or PublishModel being open.</p> |
430 |
|
delete_item(Nidx, Publisher, PublishModel, ItemId) -> |
431 |
10 |
GenKey = jid:to_bare(jid:to_lower(Publisher)), |
432 |
10 |
{ok, GenState} = mod_pubsub_db_backend:get_state(Nidx, GenKey), |
433 |
10 |
#pubsub_state{affiliation = Affiliation, items = Items} = GenState, |
434 |
10 |
Allowed = get_permition(Affiliation, PublishModel, Nidx, ItemId, GenKey), |
435 |
10 |
case Allowed of |
436 |
|
false -> |
437 |
2 |
{error, mongoose_xmpp_errors:forbidden()}; |
438 |
|
true -> |
439 |
8 |
case lists:member(ItemId, Items) of |
440 |
|
true -> |
441 |
8 |
del_item(Nidx, ItemId), |
442 |
8 |
mod_pubsub_db_backend:remove_items(Nidx, GenKey, [ItemId]), |
443 |
8 |
{result, {default, broadcast}}; |
444 |
|
false -> |
445 |
:-( |
delete_foreign_item(Nidx, ItemId, Affiliation) |
446 |
|
end |
447 |
|
end. |
448 |
|
|
449 |
:-( |
get_permition(publisher, _PublishModel, _Nidx, _ItemId, _GenKey) -> true; |
450 |
4 |
get_permition(owner, _PublishModel, _Nidx, _ItemId, _GenKey) -> true; |
451 |
2 |
get_permition(_Affiliation, open, _Nidx, _ItemId, _GenKey) -> true; |
452 |
|
get_permition(_Affiliation, _PublishModel, Nidx, ItemId, GenKey) -> |
453 |
4 |
case get_item(Nidx, ItemId) of |
454 |
2 |
{result, #pubsub_item{creation = {_, GenKey}}} -> true; |
455 |
2 |
_ -> false |
456 |
|
end. |
457 |
|
|
458 |
|
%% Delete an item that does not belong to the user |
459 |
|
%% TODO: Whole function should be moved to DB layer but we need to migrate pubsub_item first |
460 |
|
delete_foreign_item(Nidx, ItemId, owner) -> |
461 |
:-( |
{ok, States} = mod_pubsub_db_backend:get_states(Nidx), |
462 |
:-( |
lists:foldl(fun |
463 |
|
(#pubsub_state{stateid = {User, _}, items = PI}, Res) -> |
464 |
:-( |
case lists:member(ItemId, PI) of |
465 |
|
true -> |
466 |
:-( |
del_item(Nidx, ItemId), |
467 |
:-( |
mod_pubsub_db_backend:remove_items(Nidx, User, [ItemId]), |
468 |
:-( |
{result, {default, broadcast}}; |
469 |
|
false -> |
470 |
:-( |
Res |
471 |
|
end; |
472 |
|
(_, Res) -> |
473 |
:-( |
Res |
474 |
|
end, |
475 |
|
{error, mongoose_xmpp_errors:item_not_found()}, States); |
476 |
|
delete_foreign_item(_Nidx, _ItemId, _Affiliation) -> |
477 |
:-( |
{error, mongoose_xmpp_errors:item_not_found()}. |
478 |
|
|
479 |
|
purge_node(Nidx, Owner) -> |
480 |
8 |
case mod_pubsub_db_backend:get_affiliation(Nidx, jid:to_lower(Owner)) of |
481 |
|
{ok, owner} -> |
482 |
4 |
{ok, States} = mod_pubsub_db_backend:get_states(Nidx), |
483 |
4 |
lists:foreach(fun |
484 |
|
(#pubsub_state{items = []}) -> |
485 |
:-( |
ok; |
486 |
|
(#pubsub_state{items = Items}) -> |
487 |
4 |
del_items(Nidx, Items) |
488 |
|
end, |
489 |
|
States), |
490 |
4 |
mod_pubsub_db_backend:remove_all_items(Nidx), |
491 |
4 |
{result, {default, broadcast}}; |
492 |
|
_ -> |
493 |
4 |
{error, mongoose_xmpp_errors:forbidden()} |
494 |
|
end. |
495 |
|
|
496 |
|
get_entity_affiliations(Host, #jid{} = Owner) -> |
497 |
:-( |
get_entity_affiliations(Host, jid:to_lower(Owner)); |
498 |
|
get_entity_affiliations(Host, LOwner) -> |
499 |
366 |
{ok, States} = mod_pubsub_db_backend:get_states_by_bare(LOwner), |
500 |
366 |
HT = mod_pubsub:host_to_host_type(Host), |
501 |
366 |
NodeTree = mod_pubsub:tree(HT), |
502 |
366 |
Reply = lists:foldl(fun (#pubsub_state{stateid = {_, N}, affiliation = A}, Acc) -> |
503 |
103 |
case gen_pubsub_nodetree:get_node(NodeTree, N) of |
504 |
89 |
#pubsub_node{nodeid = {Host, _}} = Node -> [{Node, A} | Acc]; |
505 |
14 |
_ -> Acc |
506 |
|
end |
507 |
|
end, |
508 |
|
[], States), |
509 |
366 |
{result, Reply}. |
510 |
|
|
511 |
|
get_node_affiliations(Nidx) -> |
512 |
6 |
{ok, States} = mod_pubsub_db_backend:get_states(Nidx), |
513 |
6 |
Tr = fun (#pubsub_state{stateid = {J, _}, affiliation = A}) -> {J, A} end, |
514 |
6 |
{result, lists:map(Tr, States)}. |
515 |
|
|
516 |
|
get_affiliation(Nidx, Owner) -> |
517 |
186 |
{ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, jid:to_lower(Owner)), |
518 |
186 |
{result, Affiliation}. |
519 |
|
|
520 |
|
set_affiliation(Nidx, JID, Affiliation) -> |
521 |
54 |
mod_pubsub_db_backend:set_affiliation(Nidx, JID, Affiliation). |
522 |
|
|
523 |
|
get_entity_subscriptions(Host, Owner) -> |
524 |
509 |
LOwner = jid:to_lower(Owner), |
525 |
509 |
States = case Owner#jid.lresource of |
526 |
|
<<>> -> |
527 |
12 |
{ok, States0} = mod_pubsub_db_backend:get_states_by_lus(LOwner), |
528 |
12 |
States0; |
529 |
|
_ -> |
530 |
497 |
{ok, States0} = mod_pubsub_db_backend:get_states_by_bare_and_full(LOwner), |
531 |
497 |
States0 |
532 |
|
end, |
533 |
509 |
HT = mod_pubsub:host_to_host_type(Host), |
534 |
509 |
NodeTree = mod_pubsub:tree(HT), |
535 |
509 |
Reply = lists:foldl(fun (PubSubState, Acc) -> |
536 |
88 |
get_entity_subscriptions_loop(NodeTree, PubSubState, Acc) |
537 |
|
end, |
538 |
|
[], States), |
539 |
509 |
{result, Reply}. |
540 |
|
|
541 |
|
get_entity_subscriptions_loop(NodeTree, #pubsub_state{stateid = {J, N}, subscriptions = Ss}, Acc) -> |
542 |
88 |
case gen_pubsub_nodetree:get_node(NodeTree, N) of |
543 |
|
#pubsub_node{} = Node -> |
544 |
88 |
lists:foldl(fun ({Sub, SubId}, Acc2) -> [{Node, Sub, SubId, J} | Acc2] end, Acc, Ss); |
545 |
|
_ -> |
546 |
:-( |
Acc |
547 |
|
end. |
548 |
|
|
549 |
|
get_node_subscriptions(Nidx) -> |
550 |
574 |
{ok, Subscriptions} = mod_pubsub_db_backend:get_node_subscriptions(Nidx), |
551 |
574 |
{result, Subscriptions}. |
552 |
|
|
553 |
|
get_subscriptions(Nidx, #jid{} = Owner) -> |
554 |
5 |
get_subscriptions(Nidx, jid:to_lower(Owner)); |
555 |
|
get_subscriptions(Nidx, LOwner) -> |
556 |
23 |
{ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LOwner), |
557 |
23 |
{result, Subscriptions}. |
558 |
|
|
559 |
|
set_subscriptions(Nidx, #jid{} = Owner, Subscription, SubId) -> |
560 |
5 |
set_subscriptions(Nidx, jid:to_lower(Owner), Subscription, SubId); |
561 |
|
set_subscriptions(Nidx, LOwner, Subscription, SubId) -> |
562 |
13 |
{ok, Subscriptions} = mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LOwner), |
563 |
13 |
case {SubId, Subscriptions} of |
564 |
|
{_, []} -> |
565 |
6 |
case Subscription of |
566 |
|
none -> |
567 |
:-( |
{error, |
568 |
|
?ERR_EXTENDED((mongoose_xmpp_errors:bad_request()), <<"not-subscribed">>)}; |
569 |
|
_ -> |
570 |
6 |
NewSubId = make_subid(), |
571 |
6 |
mod_pubsub_db_backend:add_subscription(Nidx, LOwner, Subscription, NewSubId, []) |
572 |
|
end; |
573 |
|
{<<>>, [{_, SID, _}]} -> |
574 |
2 |
case Subscription of |
575 |
2 |
none -> mod_pubsub_db_backend:delete_subscription(Nidx, LOwner, SID); |
576 |
:-( |
_ -> mod_pubsub_db_backend:update_subscription(Nidx, LOwner, Subscription, SID) |
577 |
|
end; |
578 |
|
{<<>>, [_ | _]} -> |
579 |
:-( |
{error, |
580 |
|
?ERR_EXTENDED((mongoose_xmpp_errors:bad_request()), <<"subid-required">>)}; |
581 |
|
_ -> |
582 |
5 |
case Subscription of |
583 |
2 |
none -> mod_pubsub_db_backend:delete_subscription(Nidx, LOwner, SubId); |
584 |
3 |
_ -> mod_pubsub_db_backend:update_subscription(Nidx, LOwner, Subscription, SubId) |
585 |
|
end |
586 |
|
end. |
587 |
|
|
588 |
|
%% @doc <p>Returns a list of Owner's nodes on Host with pending |
589 |
|
%% subscriptions.</p> |
590 |
|
get_pending_nodes(Host, Owner) -> |
591 |
2 |
LOwner = jid:to_lower(Owner), |
592 |
2 |
{ok, Nidxs} = mod_pubsub_db_backend:get_idxs_of_own_nodes_with_pending_subs(LOwner), |
593 |
2 |
HT = mod_pubsub:host_to_host_type(Host), |
594 |
2 |
NodeTree = mod_pubsub:tree(HT), |
595 |
2 |
{result, |
596 |
|
lists:foldl(fun(N, Acc) -> |
597 |
2 |
case gen_pubsub_nodetree:get_node(NodeTree, N) of |
598 |
2 |
#pubsub_node{nodeid = {_, Node}} -> [Node | Acc]; |
599 |
:-( |
_ -> Acc |
600 |
|
end |
601 |
|
end, [], Nidxs)}. |
602 |
|
|
603 |
|
%% @doc Returns the list of stored items for a given node. |
604 |
|
%% <p>For the default PubSub module, items are stored in Mnesia database.</p> |
605 |
|
%% <p>We can consider that the pubsub_item table have been created by the main |
606 |
|
%% mod_pubsub module.</p> |
607 |
|
%% <p>PubSub plugins can store the items where they wants (for example in a |
608 |
|
%% relational database), or they can even decide not to persist any items.</p> |
609 |
|
get_items(Nidx, _From, Opts) -> |
610 |
|
%% TODO add tests and implementation supporting RSM |
611 |
|
%% when looking for node's items. |
612 |
|
%% Currently the RSM attribute is ignored |
613 |
51 |
case mod_pubsub_db_backend:get_items(Nidx, Opts) of |
614 |
|
{ok, Result} -> |
615 |
51 |
{result, Result}; |
616 |
|
{error, item_not_found} -> |
617 |
:-( |
{error, mongoose_xmpp_errors:item_not_found()} |
618 |
|
end. |
619 |
|
|
620 |
|
get_items_if_authorised(Nidx, JID, #{access_model := AccessModel, |
621 |
|
presence_permission := PresenceSubscription, |
622 |
|
roster_permission := RosterGroup, |
623 |
|
rsm := RSM} = Opts) -> |
624 |
38 |
LJID = jid:to_lower(JID), |
625 |
38 |
{ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, LJID), |
626 |
38 |
{ok, BareSubscriptions} |
627 |
|
= mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, jid:to_bare(LJID)), |
628 |
38 |
{ok, FullSubscriptions} |
629 |
|
= mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, LJID), |
630 |
38 |
Whitelisted = can_fetch_item(Affiliation, BareSubscriptions) orelse |
631 |
25 |
can_fetch_item(Affiliation, FullSubscriptions), |
632 |
38 |
case authorize_get_item(Affiliation, AccessModel, PresenceSubscription, |
633 |
|
RosterGroup, Whitelisted) of |
634 |
|
ok -> |
635 |
38 |
LowLevelOpts = #{rsm => RSM, |
636 |
|
max_items => maps:get(max_items, Opts, undefined), |
637 |
|
item_ids => maps:get(item_ids, Opts, undefined)}, |
638 |
38 |
get_items(Nidx, JID, LowLevelOpts); |
639 |
:-( |
{error, _} = Err -> Err |
640 |
|
end. |
641 |
|
|
642 |
|
%% @doc <p>Returns an item (one item list), given its reference.</p> |
643 |
|
|
644 |
|
get_item(Nidx, ItemId) -> |
645 |
127 |
case mod_pubsub_db_backend:get_item(Nidx, ItemId) of |
646 |
|
{ok, Item} -> |
647 |
8 |
{result, Item}; |
648 |
|
{error, Error} -> |
649 |
119 |
{error, Error} |
650 |
|
end. |
651 |
|
|
652 |
|
get_item(Nidx, ItemId, JID, AccessModel, PresenceSubscription, RosterGroup, _SubId) -> |
653 |
:-( |
{ok, Affiliation} = mod_pubsub_db_backend:get_affiliation(Nidx, JID), |
654 |
:-( |
{ok, Subscriptions} |
655 |
|
= mod_pubsub_db_backend:get_node_entity_subscriptions(Nidx, jid:to_bare(JID)), |
656 |
:-( |
Whitelisted = can_fetch_item(Affiliation, Subscriptions), |
657 |
:-( |
case authorize_get_item(Affiliation, AccessModel, PresenceSubscription, |
658 |
|
RosterGroup, Whitelisted) of |
659 |
:-( |
ok -> get_item(Nidx, ItemId); |
660 |
:-( |
{error, _} = Err -> Err |
661 |
|
end. |
662 |
|
|
663 |
|
authorize_get_item(Affiliation, _AccessModel, _PresenceSubscription, _RosterGroup, _Whitelisted) |
664 |
|
when (Affiliation == outcast) or (Affiliation == publish_only) -> |
665 |
:-( |
{error, mongoose_xmpp_errors:forbidden()}; |
666 |
|
authorize_get_item(_Affiliation, presence, false, _RosterGroup, _Whitelisted) -> |
667 |
:-( |
{error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_authorized()), <<"presence-subscription-required">>)}; |
668 |
|
authorize_get_item(_Affiliation, roster, _PresenceSubscription, false, _Whitelisted) -> |
669 |
:-( |
{error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_authorized()), <<"not-in-roster-group">>)}; |
670 |
|
authorize_get_item(_Affiliation, whitelist, _PresenceSubscription, _RosterGroup, false) -> |
671 |
:-( |
{error, ?ERR_EXTENDED((mongoose_xmpp_errors:not_allowed()), <<"closed-node">>)}; |
672 |
|
authorize_get_item(_Affiliation, authorize, _PresenceSubscription, _RosterGroup, false) -> |
673 |
:-( |
{error, mongoose_xmpp_errors:forbidden()}; |
674 |
|
authorize_get_item(_Affiliation, _AccessModel, _PresenceSubscription, _RosterGroup, _Whitelisted) -> |
675 |
38 |
ok. |
676 |
|
|
677 |
|
%% @doc <p>Write an item into database.</p> |
678 |
|
set_item(Item) when is_record(Item, pubsub_item) -> |
679 |
:-( |
mod_pubsub_db_backend:set_item(Item). |
680 |
|
%set_item(_) -> {error, mongoose_xmpp_errors:internal_server_error()}. |
681 |
|
|
682 |
|
%% @doc <p>Delete an item from database.</p> |
683 |
|
del_item(Nidx, ItemId) -> |
684 |
8 |
mod_pubsub_db_backend:del_item(Nidx, ItemId). |
685 |
|
|
686 |
|
del_items(Nidx, ItemIds) -> |
687 |
123 |
mod_pubsub_db_backend:del_items(Nidx, ItemIds). |
688 |
|
|
689 |
|
get_item_name(_Host, _Node, Id) -> |
690 |
:-( |
Id. |
691 |
|
|
692 |
|
%% @doc <p>Return the path of the node. In flat it's just node id.</p> |
693 |
|
node_to_path(Node) -> |
694 |
135 |
[(Node)]. |
695 |
|
|
696 |
|
path_to_node(Path) -> |
697 |
:-( |
case Path of |
698 |
|
% default slot |
699 |
:-( |
[Node] -> iolist_to_binary(Node); |
700 |
|
% handle old possible entries, used when migrating database content to new format |
701 |
|
[Node | _] when is_binary(Node) -> |
702 |
:-( |
mongoose_bin:join([<<"">> | Path], <<"/">>); |
703 |
|
% default case (used by PEP for example) |
704 |
:-( |
_ -> iolist_to_binary(Path) |
705 |
|
end. |
706 |
|
|
707 |
8 |
should_delete_when_owner_removed() -> false. |
708 |
|
|
709 |
13 |
can_fetch_item(owner, _) -> true; |
710 |
:-( |
can_fetch_item(member, _) -> true; |
711 |
:-( |
can_fetch_item(publisher, _) -> true; |
712 |
:-( |
can_fetch_item(publish_only, _) -> false; |
713 |
:-( |
can_fetch_item(outcast, _) -> false; |
714 |
50 |
can_fetch_item(none, Subscriptions) -> is_subscribed(Subscriptions). |
715 |
|
%can_fetch_item(_Affiliation, _Subscription) -> false. |
716 |
|
|
717 |
|
is_subscribed(Subscriptions) -> |
718 |
50 |
lists:any(fun |
719 |
4 |
({subscribed, _SubId, _}) -> true; |
720 |
:-( |
(_) -> false |
721 |
|
end, |
722 |
|
Subscriptions). |
723 |
|
|
724 |
|
make_subid() -> |
725 |
94 |
mongoose_bin:gen_from_timestamp(). |
726 |
|
|
727 |
|
remove_user(LUser, LServer) -> |
728 |
78 |
mod_pubsub_db_backend:dirty(fun() -> |
729 |
78 |
LJID = {LUser, LServer, <<>>}, |
730 |
78 |
mod_pubsub_db_backend:delete_user_subscriptions(LJID), |
731 |
78 |
NodesAndAffs = mod_pubsub_db_backend:find_nodes_by_affiliated_user(LJID), |
732 |
|
%% We don't broadcast node deletion notifications to subscribers because: |
733 |
|
%% * PEP nodes do not broadcast anything upon deletion |
734 |
|
%% * Push nodes do not have (should not have) any subscribers |
735 |
|
%% * Remaining nodes are not deleted when the owner leaves |
736 |
78 |
lists:foreach( |
737 |
|
fun(NodeAffPair) -> |
738 |
14 |
remove_user_by_affiliation_and_node(NodeAffPair, LJID) |
739 |
|
end, NodesAndAffs) |
740 |
|
end, #{}). |
741 |
|
|
742 |
|
-spec remove_user_by_affiliation_and_node(NodeAffPair :: {mod_pubsub:pubsubNode(), |
743 |
|
mod_pubsub:affiliation()}, |
744 |
|
LJID :: jid:ljid()) -> |
745 |
|
any(). |
746 |
|
remove_user_by_affiliation_and_node({#pubsub_node{ id = Nidx } = Node, owner}, LJID) -> |
747 |
12 |
case mod_pubsub:plugin_call(mod_pubsub:plugin(Node#pubsub_node.type), |
748 |
|
should_delete_when_owner_removed, []) of |
749 |
|
{result, true} -> |
750 |
|
% Oh my, we do have a mess in the API, don't we? |
751 |
|
% delete_node removes actual node structure |
752 |
|
% del_node removes node's items and states (affs and subs) |
753 |
4 |
mod_pubsub_db_backend:delete_node(Node), |
754 |
4 |
mod_pubsub_db_backend:del_node(Nidx); |
755 |
|
_ -> |
756 |
8 |
mod_pubsub_db_backend:set_affiliation(Nidx, LJID, none) |
757 |
|
end; |
758 |
|
remove_user_by_affiliation_and_node({#pubsub_node{ id = Nidx }, _}, LJID) -> |
759 |
2 |
mod_pubsub_db_backend:set_affiliation(Nidx, LJID, none). |
760 |
|
|