Guest User

bank_server.erl

a guest
Sep 27th, 2015
173
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Erlang 10.82 KB | None | 0 0
  1. %%%--------------------------------------------------------------------
  2. %%% @doc A simple banking module. Allows a customer to make
  3. %%% usual transactions as if in a bank.
  4. %%%--------------------------------------------------------------------
  5.  
  6. -module(bank_server).
  7.  
  8. -behaviour(gen_server).
  9.  
  10. %% API
  11. -export([start_link/0, new_user/2,
  12.     login/2, deposit/3, withdraw/3,
  13.     check_balance/2, stop/0, show_state/0]).
  14.  
  15. %% gen_server callbacks
  16. -export([init/1,
  17.          handle_call/3,
  18.          handle_cast/2,
  19.          handle_info/2,
  20.          terminate/2,
  21.          code_change/3]).
  22.  
  23. -define(SERVER, ?MODULE).
  24.  
  25. -record(state, {accounts}).
  26. -record(acct, {password, uniqueid, balance=0.0}).
  27.  
  28. %%%===================================================================
  29. %%% API
  30. %%%===================================================================
  31.  
  32. %%--------------------------------------------------------------------
  33. %% @doc
  34. %% Starts the server
  35. %%
  36. %% @spec start_link() -> {ok, Pid} | ignore | {error, Error}
  37. %% @end
  38. %%--------------------------------------------------------------------
  39. start_link() ->
  40.         gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).
  41.  
  42.  
  43. %%--------------------------------------------------------------------
  44. %% @doc
  45. %% Registers a new user account in the bank
  46. %%
  47. %% @spec new_user(Name::string(), Password::string()) -> {ok} | {error, Error}
  48. %% @end
  49. %%--------------------------------------------------------------------
  50. new_user(Name, Password) ->
  51.     gen_server:call(?SERVER, {new_user, Name, Password}).
  52.  
  53. %%--------------------------------------------------------------------
  54. %% @doc
  55. %% Interface to login using username and password, receives a unique
  56. %% token in return
  57. %%
  58. %% @spec login(Name::string(), Password::string()) -> {ok, UniqueID} | {error, Error}
  59. %% @end
  60. %%--------------------------------------------------------------------
  61. login(Name, Password) ->
  62.     gen_server:call(?SERVER, {login, Name, Password}).
  63.  
  64. %%--------------------------------------------------------------------
  65. %% @doc
  66. %% Function to deposit money on a bank account
  67. %%
  68. %% @spec deposit(Name::string(), UniqueId::string(), Amount::float()) -> {ok, success} | {error, Error}
  69. %% @end
  70. %%--------------------------------------------------------------------
  71. deposit(Name, UniqueId, Amount) ->
  72.     gen_server:call(?SERVER, {deposit, Name, UniqueId, Amount}).
  73.  
  74. %%--------------------------------------------------------------------
  75. %% @doc
  76. %% Function to withdraw money from own bank account
  77. %%
  78. %% @spec withdraw(Name::string(), UniqueId::string(), Amount::float()) ->
  79. %% {ok, success} | {error, Error}
  80. %% @end
  81. %%--------------------------------------------------------------------
  82. withdraw(Name, UniqueId, Amount) ->
  83.     gen_server:call(?SERVER, {withdraw, Name, UniqueId, Amount}).
  84.  
  85. %%--------------------------------------------------------------------
  86. %% @doc
  87. %% Check available balance on own bank account
  88. %%
  89. %% @spec check_balance(Name::string(), UniqueId::string()) -> {ok, Balance} | {error, Error}
  90. %% @end
  91. %%--------------------------------------------------------------------
  92. check_balance(Name, UniqueId) ->
  93.     gen_server:call(?SERVER, {check_balance, Name, UniqueId}).
  94.  
  95. show_state() ->
  96.     gen_server:call(?SERVER,show_state).
  97. %%--------------------------------------------------------------------
  98. %% @doc
  99. %% Stops the bank server
  100. %%
  101. %% @spec stop() -> ok
  102. %% @end
  103. %%--------------------------------------------------------------------
  104. stop() ->
  105.     gen_server:cast(?SERVER, stop).
  106.  
  107. %%%===================================================================
  108. %%% gen_server callbacks
  109. %%%===================================================================
  110.  
  111. %%--------------------------------------------------------------------
  112. %% @private
  113. %% @doc
  114. %% Initializes the server
  115. %%
  116. %% @spec init(Args) -> {ok, State} |
  117. %%                     {ok, State, Timeout} |
  118. %%                     ignore |
  119. %%                     {stop, Reason}
  120. %% @end
  121. %%--------------------------------------------------------------------
  122. init([]) ->
  123.     {ok, #state{accounts=orddict:new()}}.
  124.  
  125. %%--------------------------------------------------------------------
  126. %% @private
  127. %% @doc
  128. %% Handling call messages
  129. %%
  130. %% @spec handle_call(Request, From, State) ->
  131. %%                                   {reply, Reply, State} |
  132. %%                                   {reply, Reply, State, Timeout} |
  133. %%                                   {noreply, State} |
  134. %%                                   {noreply, State, Timeout} |
  135. %%                                   {stop, Reason, Reply, State} |
  136. %%                                   {stop, Reason, State}
  137. %% @end
  138. %%--------------------------------------------------------------------
  139. handle_call(show_state, _From, State) ->
  140.     {reply, State, State};
  141.  
  142. handle_call({new_user, Name, Password}, _From, State) ->
  143.     CheckResult = check_account_exists(Name, State#state.accounts),
  144.     check_result_for_new_user_call(CheckResult, Name, Password, State);
  145.  
  146. handle_call({login, Name, Password}, _From, State) ->
  147.     CheckResult = check_account_exists(Name, State#state.accounts),
  148.     check_result_for_login_call(CheckResult, Name, Password, State);
  149.  
  150. handle_call({deposit, Name, UniqueId, Amount}, _From, State) ->
  151.     CheckResult = check_account_exists(Name, State#state.accounts),
  152.     check_result_for_deposit_call(CheckResult, Name, UniqueId, Amount, State);
  153.  
  154. handle_call({withdraw, Name, UniqueId, Amount}, _From, State) ->
  155.     CheckResult = check_account_exists(Name, State#state.accounts),
  156.     check_result_for_withdraw_call(CheckResult, Name, UniqueId, Amount, State);
  157.  
  158. handle_call({check_balance, Name, UniqueId}, _From, State) ->
  159.     CheckResult = check_account_exists(Name, State#state.accounts),
  160.     check_result_for_check_balance_call(CheckResult, UniqueId, State).
  161.  
  162.  
  163.  
  164. %%--------------------------------------------------------------------
  165. %% @private
  166. %% @doc
  167. %% Handling cast messages
  168. %%
  169. %% @spec handle_cast(Msg, State) -> {noreply, State} |
  170. %%                                  {noreply, State, Timeout} |
  171. %%                                  {stop, Reason, State}
  172. %% @end
  173. %%--------------------------------------------------------------------
  174. handle_cast(stop, State) ->
  175.     {stop, normal, State}.
  176.  
  177. %%--------------------------------------------------------------------
  178. %% @private
  179. %% @doc
  180. %% Handling all non call/cast messages
  181. %%
  182. %% @spec handle_info(Info, State) -> {noreply, State} |
  183. %%                                   {noreply, State, Timeout} |
  184. %%                                   {stop, Reason, State}
  185. %% @end
  186. %%--------------------------------------------------------------------
  187. handle_info(_Info, State) ->
  188.     {noreply, State}.
  189.  
  190. %%--------------------------------------------------------------------
  191. %% @private
  192. %% @doc
  193. %% This function is called by a gen_server when it is about to
  194. %% terminate. It should be the opposite of Module:init/1 and do any
  195. %% necessary cleaning up. When it returns, the gen_server terminates
  196. %% with Reason. The return value is ignored.
  197. %%
  198. %% @spec terminate(Reason, State) -> void()
  199. %% @end
  200. %%--------------------------------------------------------------------
  201. terminate(_Reason, _State) ->
  202.     ok.
  203.  
  204. %%--------------------------------------------------------------------
  205. %% @private
  206. %% @doc
  207. %% Convert process state when code is changed
  208. %%
  209. %% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
  210. %% @end
  211. %%--------------------------------------------------------------------
  212. code_change(_OldVsn, State, _Extra) ->
  213.     {ok, State}.
  214.  
  215. %%%===================================================================
  216. %%% Internal functions
  217. %%%===================================================================
  218.  
  219. check_account_exists(Name, AccList) ->
  220.     check_account_exists_and_take_decision(orddict:find(Name, AccList)).
  221.  
  222. check_account_exists_and_take_decision({ok, E}) ->
  223.     {true, E};
  224. check_account_exists_and_take_decision(_Error) ->
  225.     false.
  226.  
  227. check_result_for_new_user_call(false, Name, Password, State) ->
  228.     NewAcct = orddict:store(Name, #acct{
  229.             password=Password,
  230.             balance=0.0}, State#state.accounts),
  231.     {reply, {ok}, State#state{accounts=NewAcct}};
  232. check_result_for_new_user_call(_, _Name, _Password, State) ->
  233.     {reply, {error, account_exists}, State}.
  234.  
  235. check_result_for_login_call({true, Acc}, Name, Password, State) ->
  236.     StoredPwd = Acc#acct.password,
  237.     if Password =:= StoredPwd ->
  238.             UniqueId = generate_unique_key(),
  239.             NewAcct = Acc#acct{uniqueid=UniqueId},
  240.             NewAcctList = orddict:update(Name, fun(_Old) -> NewAcct end,
  241.                     State#state.accounts),
  242.             {reply, {ok, UniqueId}, State#state{accounts = NewAcctList}};
  243.         Password =/= StoredPwd ->
  244.             {reply, {error, wrongpwd}, State}
  245.     end;
  246. check_result_for_login_call(_, _Name, _Password, State) ->
  247.     {reply, {error,nosuchuser}, State}.
  248.  
  249. check_result_for_deposit_call({true, Acc}, Name, UniqueId, Amount, State) when Amount > 0 ->
  250.     StoredUniqueId = Acc#acct.uniqueid,
  251.     if UniqueId =:= StoredUniqueId ->
  252.             NewAmount = Acc#acct.balance + Amount,
  253.             NewAcct = Acc#acct{balance=NewAmount},
  254.             NewAcctList = orddict:update(Name, fun(_Old) -> NewAcct end,
  255.                     State#state.accounts),
  256.             {reply, {ok, success}, State#state{accounts = NewAcctList}};
  257.         UniqueId =/= StoredUniqueId ->
  258.             {reply, {error, uniqueid_mismatch}, State}
  259.     end;
  260. check_result_for_deposit_call({true, _Acc}, _Name, _UniqueId, Amount, State) when Amount =< 0 ->
  261.     {reply, {error, invalid_deposit_amount}, State};
  262. check_result_for_deposit_call(_, _Name, _UniqueId, _Amount, State) ->
  263.     {reply, {error, nosuchuser}, State}.
  264.  
  265. check_result_for_withdraw_call({true, Acc}, Name, UniqueId, Amount, State) when Amount > 0 ->
  266.     StoredUniqueId = Acc#acct.uniqueid,
  267.     if UniqueId =:= StoredUniqueId ->
  268.             NewAmount = Acc#acct.balance - Amount,
  269.             if NewAmount >= 0 ->
  270.                     NewAcct = Acc#acct{balance=NewAmount},
  271.                     NewAcctList = orddict:update(Name, fun(_Old) -> NewAcct end,
  272.                             State#state.accounts),
  273.                     {reply, {ok, success}, State#state{accounts = NewAcctList}};
  274.                 NewAmount < 0 ->
  275.                     {reply, {error, not_enough_funds}, State}
  276.             end;
  277.         UniqueId =/= StoredUniqueId ->
  278.             {reply, {error, uniqueid_mismatch}, State}
  279.     end;
  280. check_result_for_withdraw_call({true, _Acc}, _Name, _UniqueId, Amount, State) when Amount =< 0 ->
  281.     {reply, {error, invalid_withdraw_amount}, State};
  282. check_result_for_withdraw_call(_, _Name, _UniqueId, _Amount, State) ->
  283.     {reply, {error, nosuchuser}, State}.
  284.  
  285. check_result_for_check_balance_call({true, Acc}, UniqueId, State) ->
  286.     StoredUniqueId = Acc#acct.uniqueid,
  287.     if UniqueId =:= StoredUniqueId ->
  288.             Balance = Acc#acct.balance,
  289.             {reply, {ok, Balance}, State};
  290.         UniqueId =/= StoredUniqueId ->
  291.             {reply, {error, uniqueid_mismatch}, State}
  292.     end;
  293. check_result_for_check_balance_call(_, _UniqueId, State) ->
  294.     {reply, {error, nosuchuser}, State}.
  295.  
  296. generate_unique_key() ->
  297.     binary:bin_to_list(base64:encode(crypto:strong_rand_bytes(16))).
  298.  
  299. %%% EOF.
Add Comment
Please, Sign In to add comment