./ct_report/coverage/mod_roster_api.COVER.html

1 %% @doc Provide an interface for frontends (like graphql or ctl) to manage roster.
2 -module(mod_roster_api).
3
4 -export([list_contacts/1,
5 get_contact/2,
6 add_contact/4,
7 delete_contact/2,
8 subscription/3,
9 subscribe_both/2,
10 set_mutual_subscription/3]).
11
12 -include("mongoose.hrl").
13 -include("jlib.hrl").
14 -include("mod_roster.hrl").
15
16 -type sub_mutual_action() :: connect | disconnect.
17
18 -define(UNKNOWN_DOMAIN_RESULT, {unknown_domain, "Domain not found"}).
19 -define(INTERNAL_ERROR_RESULT(Error, Operation),
20 {internal, io_lib:format("Cannot ~p a contact because: ~p", [Operation, Error])}).
21
22 -spec add_contact(jid:jid(), jid:jid(), binary(), [binary()]) ->
23 {ok | internal | user_not_exist | unknown_domain, iolist()}.
24 add_contact(#jid{lserver = LServer} = CallerJID, ContactJID, Name, Groups) ->
25 59 case mongoose_domain_api:get_domain_host_type(LServer) of
26 {ok, HostType} ->
27 57 case does_users_exist(CallerJID, ContactJID) of
28 ok ->
29 49 case mod_roster:set_roster_entry(HostType, CallerJID, ContactJID,
30 Name, Groups) of
31 ok ->
32 49 {ok, "Contact added successfully"};
33 {error, Error} ->
34
:-(
?INTERNAL_ERROR_RESULT(Error, create)
35 end;
36 Error ->
37 8 Error
38 end;
39 {error, not_found} ->
40 2 ?UNKNOWN_DOMAIN_RESULT
41 end.
42
43 -spec list_contacts(jid:jid()) -> {ok, [mod_roster:roster()]} | {unknown_domain, iolist()}.
44 list_contacts(#jid{lserver = LServer} = CallerJID) ->
45 52 case mongoose_domain_api:get_domain_host_type(LServer) of
46 {ok, HostType} ->
47 51 Acc0 = mongoose_acc:new(#{ location => ?LOCATION,
48 host_type => HostType,
49 lserver => LServer,
50 element => undefined }),
51 51 Acc1 = mongoose_acc:set(roster, show_full_roster, true, Acc0),
52 51 Acc2 = mongoose_hooks:roster_get(Acc1, CallerJID),
53 51 {ok, mongoose_acc:get(roster, items, Acc2)};
54 {error, not_found} ->
55 1 ?UNKNOWN_DOMAIN_RESULT
56 end.
57
58 -spec get_contact(jid:jid(), jid:jid()) ->
59 {ok, mod_roster:roster()} | {contact_not_found | internal | unknown_domain, iolist()}.
60 get_contact(#jid{lserver = LServer} = UserJID, ContactJID) ->
61 5 case mongoose_domain_api:get_domain_host_type(LServer) of
62 {ok, HostType} ->
63 4 ContactL = jid:to_lower(ContactJID),
64 4 case mod_roster:get_roster_entry(HostType, UserJID, ContactL, full) of
65 #roster{} = R ->
66 2 {ok, R};
67 does_not_exist ->
68 2 {contact_not_found, "Given contact does not exist"};
69 error ->
70
:-(
?INTERNAL_ERROR_RESULT(error, get)
71 end;
72 {error, not_found} ->
73 1 ?UNKNOWN_DOMAIN_RESULT
74 end.
75
76 -spec delete_contact(jid:jid(), jid:jid()) ->
77 {ok | contact_not_found | internal | unknown_domain, iolist()}.
78 delete_contact(#jid{lserver = LServer} = CallerJID, ContactJID) ->
79 22 case mongoose_domain_api:get_domain_host_type(LServer) of
80 {ok, HostType} ->
81 20 case mod_roster:remove_from_roster(HostType, CallerJID, ContactJID) of
82 ok ->
83 15 {ok, io_lib:format("Contact ~s deleted successfully",
84 [jid:to_binary(ContactJID)])};
85 {error, does_not_exist} ->
86 5 ErrMsg = io_lib:format("Cannot remove ~s contact that does not exist",
87 [jid:to_binary(ContactJID)]),
88 5 {contact_not_found, ErrMsg};
89 {error, Error} ->
90
:-(
?INTERNAL_ERROR_RESULT(Error, delete)
91 end;
92 {error, not_found} ->
93 2 ?UNKNOWN_DOMAIN_RESULT
94 end.
95
96 -spec subscription(jid:jid(), jid:jid(), mod_roster:sub_presence()) ->
97 {ok | unknown_domain, iolist()}.
98 subscription(#jid{lserver = LServer} = CallerJID, ContactJID, Type) ->
99 53 StanzaType = atom_to_binary(Type, latin1),
100 53 El = #xmlel{name = <<"presence">>, attrs = [{<<"type">>, StanzaType}]},
101 53 case mongoose_domain_api:get_domain_host_type(LServer) of
102 {ok, HostType} ->
103 52 Acc1 = mongoose_acc:new(#{ location => ?LOCATION,
104 from_jid => CallerJID,
105 to_jid => ContactJID,
106 host_type => HostType,
107 lserver => LServer,
108 element => El }),
109 52 Acc2 = mongoose_hooks:roster_out_subscription(Acc1, CallerJID, ContactJID, Type),
110 52 ejabberd_router:route(CallerJID, ContactJID, Acc2),
111 52 {ok, io_lib:format("Subscription stanza with type ~s sent successfully", [StanzaType])};
112 {error, not_found} ->
113 1 ?UNKNOWN_DOMAIN_RESULT
114 end.
115
116 -spec set_mutual_subscription(jid:jid(), jid:jid(), sub_mutual_action()) -> {ok, iolist()}.
117 set_mutual_subscription(UserA, UserB, connect) ->
118 3 subscribe_both({UserA, <<>>, []}, {UserB, <<>>, []});
119 set_mutual_subscription(UserA, UserB, disconnect) ->
120 3 Seq = [fun() -> delete_contact(UserA, UserB) end,
121 2 fun() -> delete_contact(UserB, UserA) end],
122 3 case run_seq(Seq, ok) of
123 ok ->
124 2 {ok, "Mututal subscription removed successfully"};
125 Error ->
126 1 Error
127 end.
128
129 -spec subscribe_both({jid:jid(), binary(), [binary()]}, {jid:jid(), binary(), [binary()]}) ->
130 {ok, iolist()}.
131 subscribe_both({UserA, NameA, GroupsA}, {UserB, NameB, GroupsB}) ->
132 13 Seq = [fun() -> add_contact(UserA, UserB, NameB, GroupsB) end,
133 9 fun() -> add_contact(UserB, UserA, NameA, GroupsA) end,
134 9 fun() -> subscription(UserA, UserB, subscribe) end,
135 9 fun() -> subscription(UserB, UserA, subscribe) end,
136 9 fun() -> subscription(UserA, UserB, subscribed) end,
137 9 fun() -> subscription(UserB, UserA, subscribed) end],
138 13 case run_seq(Seq, ok) of
139 ok ->
140 9 {ok, io_lib:format("Subscription between users ~p and ~p created successfully",
141 [jid:to_binary(UserA), jid:to_binary(UserB)])};
142 Error ->
143 4 Error
144 end.
145
146 %% Internal
147
148 -spec does_users_exist(jid:jid(), jid:jid()) -> ok | {user_not_exist, iolist()}.
149 does_users_exist(User, Contact) ->
150 57 case lists:filter(fun(U) -> not ejabberd_auth:does_user_exist(U) end, [User, Contact]) of
151 [] ->
152 49 ok;
153 [NotExist | _]->
154 8 {user_not_exist, io_lib:format("The user ~s does not exist",
155 [jid:to_binary(NotExist)])}
156 end.
157
158 -spec run_seq([fun(() -> any())], term()) -> ok | {atom(), iolist()}.
159 run_seq([Cmd | Seq], ok) ->
160 16 run_seq(Seq, Cmd());
161 run_seq([Cmd | Seq], {ok, _}) ->
162 47 run_seq(Seq, Cmd());
163 11 run_seq([], _) -> ok;
164 run_seq(_, {_, _} = Error) ->
165 5 Error.
Line Hits Source