users.erl 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. -module(users).
  2. -include("user.hrl").
  3. -include("accounts.hrl").
  4. -include("log.hrl").
  5. -include_lib("white_rabbit/include/nsm_mq.hrl").
  6. -compile(export_all).
  7. do_register(#user{username=U} = RegisterData0) ->
  8. case check_register_data(RegisterData0) of
  9. ok ->
  10. HashedPassword = case RegisterData0#user.password of
  11. undefined -> undefined;
  12. PlainPassword -> utils:sha(PlainPassword)
  13. end,
  14. RegisterData = RegisterData0#user{
  15. feed = store:feed_create(),
  16. direct = store:feed_create(),
  17. pinned = store:feed_create(),
  18. starred = store:feed_create(),
  19. password = HashedPassword},
  20. store:put(RegisterData),
  21. %nsx_msg:notify(["system", "put"], RegisterData),
  22. nsm_accounts:create_account(U),
  23. % assign quota
  24. {ok, DefaultQuota} = store:get(config, "accounts/default_quota", 300),
  25. nsm_accounts:transaction(U, ?CURRENCY_QUOTA, DefaultQuota, #ti_default_assignment{}),
  26. %% init message queues infrastructure
  27. init_mq(U, []),
  28. login_posthook(U),
  29. {ok, U};
  30. {error, Error} -> {error, Error}
  31. end.
  32. register(#user{username=U, email=Email, facebook_id = FbId} = RegisterData0) ->
  33. FindUser = case check_username(U, FbId) of
  34. {error, E} -> {error, E};
  35. {ok, NewName} ->
  36. case get_user({email, Email}) of
  37. {error, _NotFound} -> {ok, NewName};
  38. {ok, _} -> {error, email_taken}
  39. end
  40. end,
  41. FindUser2 = case FindUser of
  42. {ok, UserName} ->
  43. case nsm_groups:get_group(UserName) of
  44. {error, notfound} -> {ok, UserName}; % it means username is free
  45. _ -> {error, username_taken}
  46. end;
  47. SomethingElse -> SomethingElse
  48. end,
  49. case FindUser2 of
  50. {ok, Name} -> do_register(RegisterData0#user{username=Name});
  51. {error, username_taken} -> {error, user_exist};
  52. {error, email_taken} -> {error, email_taken}
  53. end.
  54. check_username(Name, FbId) ->
  55. case get_user(Name) of
  56. {error, notfound} -> {ok, Name};
  57. {ok, User} when FbId =/= undefined ->
  58. check_username(User#user.username ++ integer_to_list(crypto:rand_uniform(0,10)), FbId);
  59. {ok, _User}-> {error, username_taken}
  60. end.
  61. % @doc
  62. % Removes user with all tigth connected entries relying on user_id
  63. delete_user(UserName) ->
  64. % get User record
  65. case get_user(UserName) of
  66. {ok, User} ->
  67. %% remove from all groups
  68. GIds = nsm_groups:list_groups_per_user(UserName),
  69. [nsx_msg:notify(["subscription", "user", UserName, "remove_from_group"], {GId}) || GId <- GIds],
  70. %% remove from subcribtions
  71. S = list_subscr(User),
  72. F2U = [ {MeId, FrId} || #subscription{who = MeId, whom = FrId} <- S ],
  73. [ unsubscr_user(MeId, FrId) || {MeId, FrId} <- F2U ],
  74. [ unsubscr_user(FrId, MeId) || {MeId, FrId} <- F2U ],
  75. store:delete(user_status, UserName),
  76. store:delete(user, UserName),
  77. {ok, User};
  78. E -> E
  79. end.
  80. get_user({username, UserName}) -> store:user_by_username(UserName);
  81. get_user({facebook, FBId}) -> store:user_by_facebook_id(FBId);
  82. get_user({email, Email}) -> store:user_by_email(Email);
  83. get_user(UId) -> store:get(user, UId).
  84. get_all_users() -> store:all(user).
  85. subscribe(Who, Whom) ->
  86. case is_user_blocked(Who, Whom) of
  87. false ->
  88. Record = #subs{who = Who, whom = Whom},
  89. ok = store:put(Record),
  90. subscribe_user_mq(user, Who, Whom);
  91. true -> do_nothing
  92. end.
  93. unsubscribe(Who, Whom) ->
  94. case is_user_subscr(Who, Whom) of
  95. true ->
  96. ok = store:delete(subs, {Who, Whom}),
  97. remove_subscription_mq(user, Who, Whom);
  98. false ->
  99. do_nothing
  100. end.
  101. list_subscr(undefined)-> [];
  102. list_subscr(#user{username = UId}) -> list_subscr(UId);
  103. list_subscr(UId) when is_list(UId) -> lists:sort( store:all_by_index(subs, <<"subs_who_bin">>, list_to_binary(UId)) ).
  104. list_subscr(UId, PageNumber, PageAmount) when is_list(UId) ->
  105. Offset = case (PageNumber-1)*PageAmount of
  106. I when is_integer(I), I>0 -> I+1;
  107. _ -> 1
  108. end,
  109. lists:sublist(list_subscr(UId), Offset, PageAmount).
  110. list_subscr_usernames(UId) -> [UserId || #subs{whom = UserId} <- list_subscr(UId)].
  111. list_subscr_me(#user{username = UId}) -> list_subscr_me(UId);
  112. list_subscr_me(UId) when is_list(UId) -> lists:sort( store:all_by_index(subs, <<"subs_whom_bin">>, list_to_binary(UId)) ).
  113. is_user_subscr(Who, Whom) ->
  114. case store:get(subs, {Who, Whom}) of
  115. {ok, _} -> ?INFO("User ~p is a friend of user ~p", [Whom, Who]), true;
  116. _Response -> ?INFO("User ~p is not a friend of user ~p. Response: ~p", [Whom, Who, _Response]), false
  117. end.
  118. update_after_login(User) -> %RPC to cleanup
  119. Update =
  120. case user_status(User) of
  121. {error, status_info_not_found} ->
  122. #user_status{username = User,
  123. last_login = erlang:now()};
  124. {ok, UserStatus} ->
  125. UserStatus#user_status{last_login = erlang:now()}
  126. end,
  127. store:put(Update).
  128. user_status(User) ->
  129. case store:get(user_status, User) of
  130. {ok, Status} ->
  131. {ok, Status};
  132. {error, notfound} ->
  133. {error, status_info_not_found}
  134. end.
  135. user_status(User, Key) ->
  136. case user_status(User) of
  137. {ok, Status0} ->
  138. Fields = record_info(fields, user_status),
  139. [user_status | List] = tuple_to_list(Status0),
  140. Status = lists:zip(Fields, List),
  141. {_, V} = lists:keyfind(Key, 1, Status),
  142. {ok, V};
  143. _ ->
  144. {error, status_info_not_found}
  145. end.
  146. user_status(User, Key, Value) ->
  147. case user_status(User) of
  148. {ok, Status0} ->
  149. Fields = record_info(fields, user_status),
  150. [user_status | List] = tuple_to_list(Status0),
  151. Status = lists:zip(Fields, List),
  152. NewStatus0 = lists:keyreplace(Key, 1, Status, {Key, Value}),
  153. NewStatus = [user_status | element(2, lists:unzip(NewStatus0))],
  154. store:put(list_to_tuple(NewStatus));
  155. _ ->
  156. {error, status_info_not_found}
  157. end.
  158. get_user_by_feed_id(Fid) ->
  159. store:select(user, fun(#user{feed=F}) when F=:=Fid-> true;(_)->false end).
  160. search_user("") ->
  161. store:all(user);
  162. search_user(Str) ->
  163. store:select(user,
  164. fun(#user{email=E}) when E=:=Str-> true;
  165. (#user{username=N}) when N=:=Str-> true;
  166. (#user{name=N}) when N=:=Str-> true;
  167. (#user{surname=S}) when S=:=Str-> true;
  168. (#user{surname=S, name=N}) ->
  169. case S ++ " " ++ N of
  170. Sum when Sum=:=Str -> true;
  171. _ -> false
  172. end;
  173. (_) ->false
  174. end).
  175. get_user_game_status(User) ->
  176. case store:get(user_game_status, User) of
  177. {ok, #user_game_status{status=Status}} -> Status
  178. ;_ -> "offline"
  179. end.
  180. set_user_game_status(User, Status) -> store:put(#user_game_status{user=User, status=Status}).
  181. % TODO: game_session:525 move real DB operation from here behind rabbit
  182. % two level: first message received by session pid
  183. % then it goes to change db bg worker
  184. block(Who, Whom) ->
  185. ?INFO("~w:block_user/2 Who=~p Whom=~p", [?MODULE, Who, Whom]),
  186. unsubscr_user(Who, Whom),
  187. store:block_user(Who, Whom),
  188. nsx_msg:notify_user_block(Who, Whom).
  189. unblock(Who, Whom) ->
  190. ?INFO("~w:unblock_user/2 Who=~p Whom=~p", [?MODULE, Who, Whom]),
  191. store:unblock_user(Who, Whom),
  192. nsx_msg:notify_user_unblock(Who, Whom).
  193. blocked_users(UserId) -> store:list_blocks(UserId).
  194. get_blocked_users_feed_id(UserId) ->
  195. UsersId = store:list_blocks(UserId),
  196. Users = store:select(user, fun(#user{username=U})-> lists:member(U, UsersId) end),
  197. {UsersId, [Fid || #user{feed=Fid} <- Users]}.
  198. is_user_blocked(Who, Whom) -> store:is_user_blocked(Who,Whom).
  199. update_user(#user{username=UId,name=Name,surname=Surname} = NewUser) ->
  200. OldUser = case store:get(user,UId) of
  201. {error,notfound} -> NewUser;
  202. {ok,#user{}=User} -> User
  203. end,
  204. store:put(NewUser),
  205. case Name==OldUser#user.name andalso Surname==OldUser#user.surname of
  206. true -> ok;
  207. false -> store:update_user_name(UId,Name,Surname)
  208. end.
  209. subscribe_user_mq(Type, MeId, ToId) -> process_subscription_mq(Type, add, MeId, ToId).
  210. remove_subscription_mq(Type, MeId, ToId) -> process_subscription_mq(Type, delete, MeId, ToId).
  211. process_subscription_mq(Type, Action, MeId, ToId) ->
  212. {ok, Channel} = nsm_mq:open([]),
  213. Routes = case Type of
  214. user ->
  215. rk_user_feed(ToId);
  216. group ->
  217. rk_group_feed(ToId)
  218. end,
  219. case Action of
  220. add ->
  221. bind_user_exchange(Channel, MeId, Routes);
  222. delete ->
  223. catch(unbind_user_exchange(Channel, MeId, Routes))
  224. end,
  225. nsm_mq_channel:close(Channel),
  226. ok.
  227. init_mq(User, Groups) ->
  228. ?INFO("~p init mq. nsm_users: ~p", [User, Groups]),
  229. UserExchange = ?USER_EXCHANGE(User),
  230. %% we need fanout exchange to give all information to all users queues
  231. ExchangeOptions = [{type, <<"fanout">>},
  232. durable,
  233. {auto_delete, false}],
  234. {ok, Channel} = nsm_mq:open([]),
  235. ?INFO("Cration Exchange: ~p,",[{Channel,UserExchange,ExchangeOptions}]),
  236. ok = nsm_mq_channel:create_exchange(Channel, UserExchange,
  237. ExchangeOptions),
  238. ?INFO("Created OK"),
  239. %% build routing keys for user's relations
  240. Relations = build_user_relations(User, Groups),
  241. %% RK = Routing Key. Bind exchange to all user related keys.
  242. [bind_user_exchange(Channel, User, RK)
  243. || RK <- [rk([feed, delete, User])|Relations]],
  244. nsm_mq_channel:close(Channel),
  245. ok.
  246. init_mq_for_user(User) ->
  247. init_mq(User, nsm_groups:list_groups_per_user(User) ).
  248. build_user_relations(User, Groups) ->
  249. %% Feed Keys. Subscribe for self events, system and groups events
  250. %% feed.FeedOwnerType.FeedOwnerId.ElementType.ElementId.Action
  251. %% feed.system.ElementType.Action
  252. [rk_user_feed(User),
  253. %% API
  254. rk( [db, user, User, put] ),
  255. rk( [subscription, user, User, add_to_group]),
  256. rk( [subscription, user, User, remove_from_group]),
  257. rk( [subscription, user, User, leave_group]),
  258. rk( [login, user, User, update_after_login]),
  259. rk( [likes, user, User, add_like]),
  260. rk( [personal_score, user, User, add]),
  261. rk( [feed, user, User, count_entry_in_statistics] ),
  262. rk( [feed, user, User, count_comment_in_statistics] ),
  263. rk( [feed, user, User, post_note] ),
  264. rk( [subscription, user, User, subscribe_user]),
  265. rk( [subscription, user, User, remove_subscribe]),
  266. rk( [subscription, user, User, set_user_game_status]),
  267. rk( [subscription, user, User, update_user]),
  268. rk( [subscription, user, User, block_user]),
  269. rk( [subscription, user, User, unblock_user]),
  270. rk( [affiliates, user, User, create_affiliate]),
  271. rk( [affiliates, user, User, delete_affiliate]),
  272. rk( [affiliates, user, User, enable_to_look_details]),
  273. rk( [affiliates, user, User, disable_to_look_details]),
  274. rk( [purchase, user, User, set_purchase_external_id]),
  275. rk( [purchase, user, User, set_purchase_state]),
  276. rk( [purchase, user, User, set_purchase_info]),
  277. rk( [purchase, user, User, add_purchase]),
  278. rk( [transaction, user, User, add_transaction]),
  279. rk( [invite, user, User, add_invite_to_issuer]),
  280. rk( [tournaments, user, User, create]),
  281. rk( [tournaments, user, User, create_and_join]),
  282. rk( [gifts, user, User, buy_gift]),
  283. rk( [gifts, user, User, give_gift]),
  284. rk( [gifts, user, User, mark_gift_as_deliving]),
  285. %% system message format: feed.system.ElementType.Action
  286. rk( [feed, system, '*', '*']) |
  287. [rk_group_feed(G) || G <- Groups]].
  288. bind_user_exchange(Channel, User, RoutingKey) ->
  289. {bind, RoutingKey, nsm_mq_channel:bind_exchange(Channel, ?USER_EXCHANGE(User), ?NOTIFICATIONS_EX, RoutingKey)}.
  290. unbind_user_exchange(Channel, User, RoutingKey) ->
  291. {unbind, RoutingKey, nsm_mq_channel:unbind_exchange(Channel, ?USER_EXCHANGE(User), ?NOTIFICATIONS_EX, RoutingKey)}.
  292. bind_group_exchange(Channel, Group, RoutingKey) ->
  293. {bind, RoutingKey, nsm_mq_channel:bind_exchange(Channel, ?GROUP_EXCHANGE(Group), ?NOTIFICATIONS_EX, RoutingKey)}.
  294. unbind_group_exchange(Channel, Group, RoutingKey) ->
  295. {unbind, RoutingKey, nsm_mq_channel:unbind_exchange(Channel, ?GROUP_EXCHANGE(Group), ?NOTIFICATIONS_EX, RoutingKey)}.
  296. init_mq_for_group(Group) ->
  297. GroupExchange = ?GROUP_EXCHANGE(Group),
  298. ExchangeOptions = [{type, <<"fanout">>},
  299. durable,
  300. {auto_delete, false}],
  301. {ok, Channel} = nsm_mq:open([]),
  302. ok = nsm_mq_channel:create_exchange(Channel, GroupExchange, ExchangeOptions),
  303. Relations = build_group_relations(Group),
  304. [bind_group_exchange(Channel, Group, RK) || RK <- Relations],
  305. nsm_mq_channel:close(Channel),
  306. ok.
  307. build_group_relations(Group) ->
  308. [
  309. rk( [db, group, Group, put] ),
  310. rk( [db, group, Group, update_group] ),
  311. rk( [db, group, Group, remove_group] ),
  312. rk( [likes, group, Group, add_like]), % for comet mostly
  313. rk( [feed, delete, Group] ),
  314. rk( [feed, group, Group, '*', '*', '*'] )
  315. ].
  316. rk(List) ->
  317. nsm_mq_lib:list_to_key(List).
  318. rk_user_feed(User) ->
  319. rk([feed, user, User, '*', '*', '*']).
  320. rk_group_feed(Group) ->
  321. rk([feed, group, Group, '*', '*', '*']).
  322. retrieve_connections(Id,Type) ->
  323. Friends = case Type of
  324. user -> nsm_users:list_subscr_usernames(Id);
  325. _ -> nsm_groups:list_group_members(Id) end,
  326. case Friends of
  327. [] -> [];
  328. Full -> Sub = lists:sublist(Full, 10),
  329. case Sub of
  330. [] -> [];
  331. _ -> Data = [begin case store:get(user,Who) of
  332. {ok,User} -> RealName = nsm_users:user_realname_user(User),
  333. Paid = nsm_accounts:user_paid(Who),
  334. {Who,Paid,RealName};
  335. _ -> undefined end end || Who <- Sub],
  336. [X||X<-Data, X/=undefined] end end.