1 |
|
%% @doc Provide an interface for frontends (like graphql or ctl) to manage vcard. |
2 |
|
-module(mod_vcard_api). |
3 |
|
|
4 |
|
-include("mongoose.hrl"). |
5 |
|
-include("jlib.hrl"). |
6 |
|
-include("mod_vcard.hrl"). |
7 |
|
|
8 |
|
-type vcard_map() :: #{binary() => vcard_subelement_binary() | vcard_subelement_map()}. |
9 |
|
-type vcard_subelement_binary() :: binary() | [{ok, binary()}]. |
10 |
|
-type vcard_subelement_map() :: #{binary() => binary() | [{ok, binary()}]}. |
11 |
|
|
12 |
|
-export([set_vcard/2, |
13 |
|
get_vcard/1]). |
14 |
|
|
15 |
|
-spec set_vcard(jid:jid(), vcard_map()) -> |
16 |
|
{ok, vcard_map()} | {user_not_found, string()} | {internal, string()} | {vcard_not_found, string()}. |
17 |
|
set_vcard(#jid{luser = LUser, lserver = LServer} = UserJID, Vcard) -> |
18 |
9 |
case check_user(UserJID) of |
19 |
|
{ok, HostType} -> |
20 |
5 |
case set_vcard(HostType, UserJID, Vcard) of |
21 |
|
ok -> |
22 |
5 |
get_vcard_from_db(HostType, LUser, LServer); |
23 |
|
_ -> |
24 |
:-( |
{internal, "Internal server error"} |
25 |
|
end; |
26 |
|
Error -> |
27 |
4 |
Error |
28 |
|
end. |
29 |
|
|
30 |
|
-spec get_vcard(jid:jid()) -> |
31 |
|
{ok, vcard_map()} | {user_not_found, string()} | {internal, string()} | {vcard_not_found, string()} |
32 |
|
| {vcard_not_configured_error, string()}. |
33 |
|
get_vcard(#jid{luser = LUser, lserver = LServer} = UserJID) -> |
34 |
|
% check if mod_vcard is loaded is needed in user's get_vcard command, when user variable is not passed |
35 |
20 |
case check_user(UserJID) of |
36 |
|
{ok, HostType} -> |
37 |
14 |
case gen_mod:is_loaded(HostType, mod_vcard) of |
38 |
|
true -> |
39 |
13 |
get_vcard_from_db(HostType, LUser, LServer); |
40 |
|
false -> |
41 |
1 |
{vcard_not_configured_error, "Mod_vcard is not loaded for this host"} |
42 |
|
end; |
43 |
|
Error -> |
44 |
6 |
Error |
45 |
|
end. |
46 |
|
|
47 |
|
set_vcard(HostType, UserJID, Vcard) -> |
48 |
5 |
mod_vcard:unsafe_set_vcard(HostType, UserJID, transform_from_map(Vcard)). |
49 |
|
|
50 |
|
get_vcard_from_db(HostType, LUser, LServer) -> |
51 |
18 |
ItemNotFoundError = mongoose_xmpp_errors:item_not_found(), |
52 |
18 |
case mod_vcard_backend:get_vcard(HostType, LUser, LServer) of |
53 |
|
{ok, Result} -> |
54 |
14 |
[#xmlel{children = VcardData}] = Result, |
55 |
14 |
{ok, to_map_format(VcardData)}; |
56 |
|
{error, ItemNotFoundError} -> |
57 |
4 |
{vcard_not_found, "Vcard for user not found"}; |
58 |
|
_ -> |
59 |
:-( |
{internal, "Internal server error"} |
60 |
|
end. |
61 |
|
|
62 |
|
-spec check_user(jid:jid()) -> {ok, mongooseim:host_type()} | {user_not_found, binary()}. |
63 |
|
check_user(JID = #jid{lserver = LServer}) -> |
64 |
29 |
case mongoose_domain_api:get_domain_host_type(LServer) of |
65 |
|
{ok, HostType} -> |
66 |
24 |
case ejabberd_auth:does_user_exist(HostType, JID, stored) of |
67 |
19 |
true -> {ok, HostType}; |
68 |
5 |
false -> {user_not_found, <<"Given user does not exist">>} |
69 |
|
end; |
70 |
|
{error, not_found} -> |
71 |
5 |
{user_not_found, <<"User's domain does not exist">>} |
72 |
|
end. |
73 |
|
|
74 |
|
transform_from_map(Vcard) -> |
75 |
11 |
#xmlel{name = <<"vCard">>, |
76 |
|
attrs = [{<<"xmlns">>, <<"vcard-temp">>}], |
77 |
|
children = lists:foldl(fun({Name, Value}, Acc) -> |
78 |
319 |
Acc ++ transform_field_and_value(Name, Value) |
79 |
|
end, [], maps:to_list(Vcard))}. |
80 |
|
|
81 |
|
construct_xmlel(Name, Children) when is_list(Children)-> |
82 |
496 |
[#xmlel{name = Name, |
83 |
|
attrs = [], |
84 |
|
children = Children}]. |
85 |
|
|
86 |
|
transform_field_and_value(_Name, null) -> |
87 |
202 |
[]; |
88 |
|
transform_field_and_value(Name, Value) when is_list(Value) -> |
89 |
95 |
lists:foldl(fun(Element, Acc) -> |
90 |
194 |
Acc ++ transform_field_and_value(Name, Element) |
91 |
|
end, [], Value); |
92 |
|
transform_field_and_value(Name, Value) when is_map(Value) -> |
93 |
109 |
construct_xmlel(from_map_to_xml(Name), process_child_map(Value)); |
94 |
|
transform_field_and_value(Name, Value) -> |
95 |
107 |
construct_xmlel(from_map_to_xml(Name), [{xmlcdata, Value}]). |
96 |
|
|
97 |
|
transform_subfield_and_value(_Name, null) -> |
98 |
75 |
[]; |
99 |
|
transform_subfield_and_value(<<"vcard">>, Value) -> |
100 |
6 |
[transform_from_map(Value)]; |
101 |
|
transform_subfield_and_value(<<"tags">>, TagsList) -> |
102 |
32 |
lists:foldl(fun(Tag, Acc) -> |
103 |
61 |
Acc ++ construct_xmlel(Tag, []) |
104 |
|
end, [], TagsList); |
105 |
|
transform_subfield_and_value(Name, Value) when is_list(Value) -> |
106 |
18 |
lists:foldl(fun(Element, Acc) -> |
107 |
30 |
Acc ++ construct_xmlel(from_map_to_xml(Name), [{xmlcdata, Element}]) |
108 |
|
end, [], Value); |
109 |
|
transform_subfield_and_value(Name, Value) -> |
110 |
189 |
construct_xmlel(from_map_to_xml(Name), [{xmlcdata, Value}]). |
111 |
|
|
112 |
|
process_child_map(Value) -> |
113 |
109 |
lists:foldl(fun({Name, SubfieldValue}, Acc) -> |
114 |
320 |
Acc ++ transform_subfield_and_value(Name, SubfieldValue) |
115 |
|
end, [], maps:to_list(Value)). |
116 |
|
|
117 |
11 |
from_map_to_xml(<<"formattedName">>) -> <<"FN">>; |
118 |
11 |
from_map_to_xml(<<"nameComponents">>) -> <<"N">>; |
119 |
6 |
from_map_to_xml(<<"birthday">>) -> <<"BDAY">>; |
120 |
8 |
from_map_to_xml(<<"address">>) -> <<"ADR">>; |
121 |
6 |
from_map_to_xml(<<"telephone">>) -> <<"TEL">>; |
122 |
6 |
from_map_to_xml(<<"timeZone">>) -> <<"TZ">>; |
123 |
6 |
from_map_to_xml(<<"sortString">>) -> <<"SOR">>; |
124 |
9 |
from_map_to_xml(<<"givenName">>) -> <<"GIVEN">>; |
125 |
9 |
from_map_to_xml(<<"middleName">>) -> <<"MIDDLE">>; |
126 |
6 |
from_map_to_xml(<<"credential">>) -> <<"CRED">>; |
127 |
8 |
from_map_to_xml(<<"country">>) -> <<"CTRY">>; |
128 |
15 |
from_map_to_xml(<<"binValue">>) -> <<"BINVAL">>; |
129 |
18 |
from_map_to_xml(<<"extValue">>) -> <<"EXTVAL">>; |
130 |
316 |
from_map_to_xml(Name) -> list_to_binary(string:to_upper(binary_to_list(Name))). |
131 |
|
|
132 |
|
to_map_format(Vcard) -> |
133 |
26 |
lists:foldl(fun(#xmlel{name = Name, children = Value}, Acc) -> |
134 |
441 |
maps:merge(Acc, transform_from_xml(Name, Value, Acc)) |
135 |
|
end, #{}, Vcard). |
136 |
|
|
137 |
|
transform_from_xml(<<"FN">>, [{_, Value}], _) -> |
138 |
24 |
#{<<"formattedName">> => Value}; |
139 |
|
transform_from_xml(<<"N">>, Value, _) -> |
140 |
22 |
#{<<"nameComponents">> => lists:foldl(fun name_components_process/2, #{}, Value)}; |
141 |
|
transform_from_xml(<<"NICKNAME">>, Value, Acc) -> |
142 |
36 |
simple_process(<<"nickname">>, Value, Acc); |
143 |
|
transform_from_xml(<<"PHOTO">>, Value, Acc) -> |
144 |
36 |
complex_process(<<"photo">>, Value, Acc, fun image_components_process/2); |
145 |
|
transform_from_xml(<<"BDAY">>, Value, Acc) -> |
146 |
12 |
simple_process(<<"birthday">>, Value, Acc); |
147 |
|
transform_from_xml(<<"ADR">>, Value, Acc) -> |
148 |
18 |
complex_process(<<"address">>, Value, Acc, fun address_components_process/2); |
149 |
|
transform_from_xml(<<"LABEL">>, Value, Acc) -> |
150 |
12 |
complex_process(<<"label">>, Value, Acc, fun label_components_process/2); |
151 |
|
transform_from_xml(<<"TEL">>, Value, Acc) -> |
152 |
12 |
complex_process(<<"telephone">>, Value, Acc, fun telephone_components_process/2); |
153 |
|
transform_from_xml(<<"EMAIL">>, Value, Acc) -> |
154 |
16 |
complex_process(<<"email">>, Value, Acc, fun email_components_process/2); |
155 |
|
transform_from_xml(<<"JABBERID">>, Value, Acc) -> |
156 |
12 |
simple_process(<<"jabberId">>, Value, Acc); |
157 |
|
transform_from_xml(<<"MAILER">>, Value, Acc) -> |
158 |
12 |
simple_process(<<"mailer">>, Value, Acc); |
159 |
|
transform_from_xml(<<"TZ">>, Value, Acc) -> |
160 |
12 |
simple_process(<<"timeZone">>, Value, Acc); |
161 |
|
transform_from_xml(<<"GEO">>, Value, Acc) -> |
162 |
12 |
complex_process(<<"geo">>, Value, Acc, fun geo_components_process/2); |
163 |
|
transform_from_xml(<<"TITLE">>, Value, Acc) -> |
164 |
12 |
simple_process(<<"title">>, Value, Acc); |
165 |
|
transform_from_xml(<<"ROLE">>, Value, Acc) -> |
166 |
12 |
simple_process(<<"role">>, Value, Acc); |
167 |
|
transform_from_xml(<<"LOGO">>, Value, Acc) -> |
168 |
12 |
complex_process(<<"logo">>, Value, Acc, fun image_components_process/2); |
169 |
|
transform_from_xml(<<"AGENT">>, Value, Acc) -> |
170 |
18 |
complex_process(<<"agent">>, Value, Acc, fun agent_components_process/2); |
171 |
|
transform_from_xml(<<"ORG">>, Value, Acc) -> |
172 |
12 |
complex_process(<<"org">>, Value, Acc, fun org_components_process/2); |
173 |
|
transform_from_xml(<<"CATEGORIES">>, Value, Acc) -> |
174 |
12 |
complex_process(<<"categories">>, Value, Acc, fun categories_components_process/2); |
175 |
|
transform_from_xml(<<"NOTE">>, Value, Acc) -> |
176 |
12 |
simple_process(<<"note">>, Value, Acc); |
177 |
|
transform_from_xml(<<"PRODID">>, Value, Acc) -> |
178 |
12 |
simple_process(<<"prodId">>, Value, Acc); |
179 |
|
transform_from_xml(<<"REV">>, Value, Acc) -> |
180 |
12 |
simple_process(<<"rev">>, Value, Acc); |
181 |
|
transform_from_xml(<<"SOR">>, Value, Acc) -> |
182 |
12 |
simple_process(<<"sortString">>, Value, Acc); |
183 |
|
transform_from_xml(<<"SOUND">>, Value, Acc) -> |
184 |
18 |
complex_process(<<"sound">>, Value, Acc, fun sound_components_process/2); |
185 |
|
transform_from_xml(<<"UID">>, Value, Acc) -> |
186 |
12 |
simple_process(<<"uid">>, Value, Acc); |
187 |
|
transform_from_xml(<<"URL">>, Value, Acc) -> |
188 |
12 |
simple_process(<<"url">>, Value, Acc); |
189 |
|
transform_from_xml(<<"DESC">>, Value, Acc) -> |
190 |
12 |
simple_process(<<"desc">>, Value, Acc); |
191 |
|
transform_from_xml(<<"CLASS">>, Value, Acc) -> |
192 |
12 |
complex_process(<<"class">>, Value, Acc, fun class_components_process/2); |
193 |
|
transform_from_xml(<<"KEY">>, Value, Acc) -> |
194 |
12 |
complex_process(<<"key">>, Value, Acc, fun key_components_process/2); |
195 |
|
transform_from_xml(_, _, _) -> |
196 |
1 |
#{}. |
197 |
|
|
198 |
|
process_value([{_, Value}]) -> |
199 |
456 |
Value; |
200 |
|
process_value(_) -> |
201 |
:-( |
null. |
202 |
|
|
203 |
|
simple_process(Name, [{_, Value}], Acc) -> |
204 |
192 |
List = maps:get(Name, Acc, []), |
205 |
192 |
#{Name => List ++ [{ok, Value}]}; |
206 |
|
simple_process(_, _, _) -> |
207 |
:-( |
#{}. |
208 |
|
|
209 |
|
complex_process(Name, Value, Acc, Fun) -> |
210 |
202 |
List = maps:get(Name, Acc, []), |
211 |
202 |
#{Name => List ++ [{ok, lists:foldl(fun(Element, Accumulator) -> |
212 |
510 |
Fun(Element, Accumulator) |
213 |
|
end, #{}, Value)}]}. |
214 |
|
|
215 |
|
name_components_process(#xmlel{name = <<"FAMILY">>, children = Value}, Acc) -> |
216 |
18 |
maps:put(<<"family">>, process_value(Value), Acc); |
217 |
|
name_components_process(#xmlel{name = <<"GIVEN">>, children = Value}, Acc) -> |
218 |
18 |
maps:put(<<"givenName">>, process_value(Value), Acc); |
219 |
|
name_components_process(#xmlel{name = <<"MIDDLE">>, children = Value}, Acc) -> |
220 |
18 |
maps:put(<<"middleName">>, process_value(Value), Acc); |
221 |
|
name_components_process(#xmlel{name = <<"PREFIX">>, children = Value}, Acc) -> |
222 |
18 |
maps:put(<<"prefix">>, process_value(Value), Acc); |
223 |
|
name_components_process(#xmlel{name = <<"SUFFIX">>, children = Value}, Acc) -> |
224 |
18 |
maps:put(<<"suffix">>, process_value(Value), Acc). |
225 |
|
|
226 |
|
address_components_process(#xmlel{name = <<"POBOX">>, children = Value}, Acc) -> |
227 |
18 |
maps:put(<<"pobox">>, process_value(Value), Acc); |
228 |
|
address_components_process(#xmlel{name = <<"EXTADD">>, children = Value}, Acc) -> |
229 |
18 |
maps:put(<<"extadd">>, process_value(Value), Acc); |
230 |
|
address_components_process(#xmlel{name = <<"STREET">>, children = Value}, Acc) -> |
231 |
18 |
maps:put(<<"street">>, process_value(Value), Acc); |
232 |
|
address_components_process(#xmlel{name = <<"LOCALITY">>, children = Value}, Acc) -> |
233 |
18 |
maps:put(<<"locality">>, process_value(Value), Acc); |
234 |
|
address_components_process(#xmlel{name = <<"REGION">>, children = Value}, Acc) -> |
235 |
18 |
maps:put(<<"region">>, process_value(Value), Acc); |
236 |
|
address_components_process(#xmlel{name = <<"PCODE">>, children = Value}, Acc) -> |
237 |
14 |
maps:put(<<"pcode">>, process_value(Value), Acc); |
238 |
|
address_components_process(#xmlel{name = <<"CTRY">>, children = Value}, Acc) -> |
239 |
18 |
maps:put(<<"country">>, process_value(Value), Acc); |
240 |
|
address_components_process(#xmlel{name = Name, children = []}, Acc) -> |
241 |
42 |
List = maps:get(<<"tags">>, Acc, []), |
242 |
42 |
maps:merge(Acc, #{<<"tags">> => List ++ [{ok, Name}]}). |
243 |
|
|
244 |
|
label_components_process(#xmlel{name = <<"LINE">>, children = Value}, Acc) -> |
245 |
24 |
List = maps:get(<<"line">>, Acc, []), |
246 |
24 |
maps:merge(Acc, #{<<"line">> => List ++ [{ok, process_value(Value)}]}); |
247 |
|
label_components_process(#xmlel{name = Name, children = []}, Acc) -> |
248 |
24 |
List = maps:get(<<"tags">>, Acc, []), |
249 |
24 |
maps:merge(Acc, #{<<"tags">> => List ++ [{ok, Name}]}). |
250 |
|
|
251 |
|
telephone_components_process(#xmlel{name = <<"NUMBER">>, children = Value}, Acc) -> |
252 |
12 |
maps:put(<<"number">>, process_value(Value), Acc); |
253 |
|
telephone_components_process(#xmlel{name = Name, children = []}, Acc) -> |
254 |
24 |
List = maps:get(<<"tags">>, Acc, []), |
255 |
24 |
maps:merge(Acc, #{<<"tags">> => List ++ [{ok, Name}]}). |
256 |
|
|
257 |
|
email_components_process(#xmlel{name = <<"USERID">>, children = Value}, Acc) -> |
258 |
16 |
maps:put(<<"userId">>, process_value(Value), Acc); |
259 |
|
email_components_process(#xmlel{name = Name, children = []}, Acc) -> |
260 |
24 |
List = maps:get(<<"tags">>, Acc, []), |
261 |
24 |
maps:merge(Acc, #{<<"tags">> => List ++ [{ok, Name}]}). |
262 |
|
|
263 |
|
geo_components_process(#xmlel{name = <<"LAT">>, children = Value}, Acc) -> |
264 |
12 |
maps:put(<<"lat">>, process_value(Value), Acc); |
265 |
|
geo_components_process(#xmlel{name = <<"LON">>, children = Value}, Acc) -> |
266 |
12 |
maps:put(<<"lon">>, process_value(Value), Acc). |
267 |
|
|
268 |
|
org_components_process(#xmlel{name = <<"ORGNAME">>, children = Value}, Acc) -> |
269 |
12 |
maps:put(<<"orgname">>, process_value(Value), Acc); |
270 |
|
org_components_process(#xmlel{name = <<"ORGUNIT">>, children = Value}, Acc) -> |
271 |
18 |
List = maps:get(<<"orgunit">>, Acc, []), |
272 |
18 |
maps:merge(Acc, #{<<"orgunit">> => List ++ [{ok, process_value(Value)}]}). |
273 |
|
|
274 |
|
categories_components_process(#xmlel{name = <<"KEYWORD">>, children = Value}, Acc) -> |
275 |
18 |
List = maps:get(<<"keyword">>, Acc, []), |
276 |
18 |
maps:merge(Acc, #{<<"keyword">> => List ++ [{ok, process_value(Value)}]}). |
277 |
|
|
278 |
|
key_components_process(#xmlel{name = <<"CRED">>, children = Value}, Acc) -> |
279 |
12 |
maps:put(<<"credential">>, process_value(Value), Acc); |
280 |
|
key_components_process(#xmlel{name = <<"TYPE">>, children = Value}, Acc) -> |
281 |
12 |
maps:put(<<"type">>, process_value(Value), Acc). |
282 |
|
|
283 |
|
class_components_process(#xmlel{name = Name, children = []}, Acc) -> |
284 |
18 |
List = maps:get(<<"tags">>, Acc, []), |
285 |
18 |
maps:merge(Acc, #{<<"tags">> => List ++ [{ok, Name}]}). |
286 |
|
|
287 |
|
image_components_process(#xmlel{name = <<"TYPE">>, children = Value}, Acc) -> |
288 |
24 |
maps:put(<<"type">>, process_value(Value), Acc); |
289 |
|
image_components_process(#xmlel{name = <<"BINVAL">>, children = Value}, Acc) -> |
290 |
24 |
maps:put(<<"binValue">>, process_value(Value), Acc); |
291 |
|
image_components_process(#xmlel{name = <<"EXTVAL">>, children = Value}, Acc) -> |
292 |
24 |
maps:put(<<"extValue">>, process_value(Value), Acc). |
293 |
|
|
294 |
|
sound_components_process(#xmlel{name = <<"PHONETIC">>, children = Value}, Acc) -> |
295 |
6 |
maps:put(<<"phonetic">>, process_value(Value), Acc); |
296 |
|
sound_components_process(#xmlel{name = <<"BINVAL">>, children = Value}, Acc) -> |
297 |
6 |
maps:put(<<"binValue">>, process_value(Value), Acc); |
298 |
|
sound_components_process(#xmlel{name = <<"EXTVAL">>, children = Value}, Acc) -> |
299 |
6 |
maps:put(<<"extValue">>, process_value(Value), Acc). |
300 |
|
|
301 |
|
agent_components_process(#xmlel{name = <<"vCard">>, children = Value}, Acc) -> |
302 |
12 |
maps:put(<<"vcard">>, to_map_format(Value), Acc); |
303 |
|
agent_components_process(#xmlel{name = <<"EXTVAL">>, children = Value}, Acc) -> |
304 |
6 |
maps:put(<<"extValue">>, process_value(Value), Acc). |