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