Implemented ts_api:get_user/2, ts_api:put_user/1, and ts_api:post_user/1.
Created a utility function for OK results, ts_api:make_json_200/2 Created ts_common:new/1 and ts_common:update/1 to generalize record creation and update. Refactored ts_timeline:new/1 and ts_timeline:update/1 to use the ts_common functions. Implemented ts_json:record_to_ejson/1 and ts_json:ejson_to_record/1 for ts_user records. Implemented ts_user module.
This commit is contained in:
		
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1 +1,3 @@ | ||||
| *.sw? | ||||
| ebin/ | ||||
| *.beam | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										104
									
								
								src/ts_api.erl
									
									
									
									
									
								
							
							
						
						
									
										104
									
								
								src/ts_api.erl
									
									
									
									
									
								
							| @@ -85,11 +85,50 @@ dispatch_entry_by_date(YArg, Username, Timeline, Params) -> todo. | ||||
| % ======== IMPLEMENTATION ====== % | ||||
| % ============================== % | ||||
|  | ||||
| get_user(YArg, Username) -> todo. | ||||
| get_user(YArg, Username) -> | ||||
|     case ts_user:lookup(Username) of | ||||
|         no_record -> make_json_404(YArg); | ||||
|         User -> make_json_200(YArg, User) | ||||
|     end. | ||||
|  | ||||
| put_user(YArg, Username) -> todo. | ||||
| put_user(YArg, Username) ->  | ||||
|     % parse the request body | ||||
|     {done, {ok, EJSON}, _} = json:decode([], binary_to_list(YArg#arg.clidata)), | ||||
|  | ||||
| post_user(YArg, Username) -> todo. | ||||
|     % parse into a user record | ||||
|     NewRecord = ts_json:ejson_to_record(#ts_user{}, EJSON), | ||||
|  | ||||
|     case ts_user:new(NewRecord) of | ||||
|         % record created | ||||
|         ok -> [{status, 201}, make_json_200(YArg, NewRecord)]; | ||||
|  | ||||
|         % will not create, record exists | ||||
|         {error, {record_exists, ExistingRecord}} -> | ||||
|             JSONReturn = json:encode({struct, [ | ||||
|                 {status, "username taken"}, | ||||
|                 {see_docs, "/ts_api_doc/user#PUT"} | ||||
|             ]}), | ||||
|  | ||||
|             [{status, 409}, {content, "application/json", JSONReturn}]; | ||||
|  | ||||
|         _Error -> make_json_500(YArg) | ||||
|     end. | ||||
|  | ||||
| post_user(YArg, Username) ->  | ||||
|     % parse the POST data | ||||
|     {done, {ok, EJSON}, _} = json:decode([], binary_to_list(YArg#arg.clidata)), | ||||
|  | ||||
|     % create the user record | ||||
|     NewRecord = ts_json:ejson_to_record(#ts_user{}, EJSON), | ||||
|  | ||||
|     case ts_user:update(NewRecord) of | ||||
|         ok -> make_json_200(YArg, NewRecord); | ||||
|  | ||||
|         no_record -> make_json_404(YArg, | ||||
|             [{status, "no_record"}, {see_docs, "/ts_api_doc/user#POST"}]); | ||||
|  | ||||
|         _Error -> make_json_500(YArg) | ||||
|     end. | ||||
|  | ||||
| delete_user(YArg, Username) -> todo. | ||||
|  | ||||
| @@ -98,13 +137,7 @@ get_timeline(YArg, Username, TimelineId) -> | ||||
|  | ||||
|         no_record -> make_json_404(YArg); | ||||
|  | ||||
|         Timeline ->  | ||||
|             EJSONTimeline = ts_json:record_to_ejson(Timeline), | ||||
|             JSONReturn = json:encode({struct, [ | ||||
|                 {status, "ok"}, | ||||
|                 {timeline, EJSONTimeline} | ||||
|             ]}), | ||||
|             {content, "application/json", JSONReturn} | ||||
|         Timeline -> make_json_200(YArg, Timeline) | ||||
|     end. | ||||
|  | ||||
| put_timeline(YArg, Username, TimelineId) -> | ||||
| @@ -119,16 +152,7 @@ put_timeline(YArg, Username, TimelineId) -> | ||||
|     % insert into the database | ||||
|     case ts_timeline:new(NewRecord) of | ||||
|         % record created | ||||
|         ok -> | ||||
|  | ||||
|             EJSONRec = ts_json:record_to_ejson(NewRecord), | ||||
|             JSONReturn = json:encode({struct, [ | ||||
|                 {status, "ok"}, | ||||
|                 {timeline, EJSONRec} | ||||
|             ]}), | ||||
|  | ||||
|             % return the new record | ||||
|             [{status, 201}, {content, "application/json", JSONReturn}]; | ||||
|         ok -> [{status, 201}, make_json_200(YArg, NewRecord)]; | ||||
|  | ||||
|         % will not create, record exists | ||||
|         {error, {record_exists, ExistingRecord}} -> | ||||
| @@ -154,18 +178,10 @@ post_timeline(YArg, Username, TimelineId) -> | ||||
|     NewRecord = ts_json:ejson_to_record(EmptyTimeline, EJSON), | ||||
|  | ||||
|     case ts_timeline:update(NewRecord) of | ||||
|         ok -> | ||||
|         ok -> make_json_200(YArg, NewRecord); | ||||
|  | ||||
|             EJSONRec = ts_json:record_to_ejson(NewRecord), | ||||
|             JSONReturn = json:encode({struct, [ | ||||
|                 {status, "updated"}, | ||||
|                 {timeline, EJSONRec} | ||||
|             ]}), | ||||
|  | ||||
|             {content, "application/json", JSONReturn}; | ||||
|  | ||||
|         no_record -> make_json_404(YArg, [{status, "no_record"}, | ||||
|                 {see_docs, "/ts_api_doc/timeline#PSOT"}]); | ||||
|         no_record -> make_json_404(YArg, | ||||
|             [{status, "no_record"}, {see_docs, "/ts_api_doc/timeline#PSOT"}]); | ||||
|  | ||||
|         _Error -> make_json_500(YArg) | ||||
|     end. | ||||
| @@ -194,9 +210,7 @@ list_entries(YArg, Username, Timeline) -> | ||||
|     Entries = case SortOrder of | ||||
|         asc -> ts_entry:list_asc({Username, Timeline}, Start, Length); | ||||
|         desc -> ts_entry:list_desc({Username, Timeline}, Start, Length) | ||||
|     end | ||||
|      | ||||
|     . | ||||
|     end. | ||||
|  | ||||
| get_entry_by_id(YArg, Username, Timeline, EventId) -> | ||||
|     case ts_entry:lookup(Username, Timeline, EventId) of | ||||
| @@ -205,17 +219,7 @@ get_entry_by_id(YArg, Username, Timeline, EventId) -> | ||||
|         no_record -> make_json_404(YArg); | ||||
|  | ||||
|         % record found | ||||
|         Entry -> | ||||
|             % convert to EJSON | ||||
|             EJSONRec = ts_json:record_to_ejson(Entry), | ||||
|  | ||||
|             % create response | ||||
|             JSONReturn = json:encode({struct, [ | ||||
|                 {status, "ok"}, | ||||
|                 {entry, EJSONRec} | ||||
|             ]}), | ||||
|  | ||||
|             {content, "application/json", JSONReturn} | ||||
|         Entry -> make_json_200(YArg, Entry) | ||||
|     end. | ||||
|  | ||||
| % ============================== % | ||||
| @@ -226,6 +230,16 @@ get_entry_by_id(YArg, Username, Timeline, EventId) -> | ||||
| path_element_to_atom(PE) -> | ||||
|     list_to_atom(re:replace(PE, "\\s", "_", [{return, list}])). | ||||
|  | ||||
| %% Create a JSON 200 response. | ||||
| make_json_200(YArg, Record) -> | ||||
|     EJSONRecord = ts_json:record_to_ejson(Record), | ||||
|     JSONReturn = json:encode({struct, [ | ||||
|         {status, "ok"}, | ||||
|         {element(1, Record), EJSONRecord} | ||||
|     ]}), | ||||
|  | ||||
|     {content, "application/json", JSONReturn}. | ||||
|  | ||||
| %% Create a JSON 404 response. | ||||
| make_json_404(YArg) -> make_json_404(YArg, []). | ||||
| make_json_404(YArg, Fields) -> | ||||
|   | ||||
| @@ -1,8 +1,33 @@ | ||||
| -module(ts_common). | ||||
| -export([list/3, order_datetimes/2]). | ||||
| -export([new/1, update/1, list/3, order_datetimes/2]). | ||||
|  | ||||
| -include_lib("stdlib/include/qlc.hrl"). | ||||
|  | ||||
| new(Record) -> | ||||
|  | ||||
|     Table = element(1, Record), | ||||
|  | ||||
|     % check for existing record | ||||
|     case mnesia:dirty_read(Table, element(2, Record)) of | ||||
|         % record exists | ||||
|         [ExistingRecord] -> {error, {record_exists, ExistingRecord}}; | ||||
|          | ||||
|         [] -> mnesia:dirty_write(Record) | ||||
|     end. | ||||
|  | ||||
| update(Record) -> | ||||
|  | ||||
|     Table = element(1, Record), | ||||
|     Key = element(2, Record), | ||||
|  | ||||
|     % look for existing record | ||||
|     case mnesia:dirty_read(Table, Key) of | ||||
|         % record does not exist, cannot update | ||||
|         [] -> no_record; | ||||
|         % record does exist, update | ||||
|         [_ExistingRecord] -> mnesia:dirty_write(Record) | ||||
|     end. | ||||
|  | ||||
| %% list <Length> number of records, skipping the first <Start> | ||||
| list(Table, Start, Length) | ||||
| when is_atom(Table) and is_integer(Start) and is_integer(Length) -> | ||||
|   | ||||
| @@ -42,7 +42,7 @@ when is_integer(Start) and is_integer(Length) -> | ||||
|         MatchHead = #ts_entry{ref = {Username, Timeline, '_'}, _='_'}, | ||||
|  | ||||
|         % select all records that match | ||||
|         mnesia:select(ts_entry, [{MatchHead, [], ['$_']}] | ||||
|         mnesia:select(ts_entry, [{MatchHead, [], ['$_']}]) | ||||
|     end), | ||||
|  | ||||
|     % sort | ||||
| @@ -69,7 +69,7 @@ list({Username, Timeline}, StartDateTime, EndDateTime, OrderFun) -> | ||||
|         EndGuard = {'<', '$1', EndSeconds}, | ||||
|  | ||||
|         mnesia:select(ts_entry, [{MatchHead, [StartGuard, EndGuard], ['$_']}]) | ||||
|     end, | ||||
|     end), | ||||
|  | ||||
|     % sort | ||||
|     lists:sort(OrderFun, Entries). | ||||
|   | ||||
| @@ -5,26 +5,36 @@ | ||||
|  | ||||
| encode_record(Record) -> lists:flatten(json:encode(record_to_ejson(Record))). | ||||
|  | ||||
| record_to_ejson(Record=#ts_user{}) -> | ||||
|     {struct, [ | ||||
|         {username, atom_to_list(Record#ts_user.username)}, | ||||
|         {name, Record#ts_user.name}, | ||||
|         {email, Record#ts_user.email}, | ||||
|         {join_date, encode_datetime(Record#ts_user.join_date)}]}; | ||||
|  | ||||
| record_to_ejson(Record=#ts_timeline{}) -> | ||||
|     % pull out the timeline id | ||||
|     {_Username, TimelineId} = Record#ts_timeline.ref, | ||||
|     % pull out the username and timeline id | ||||
|     {Username, TimelineId} = Record#ts_timeline.ref, | ||||
|  | ||||
|     % create the EJSON struct | ||||
|     {struct, [ | ||||
|         {id, atom_to_list(TimelineId)}, | ||||
|         {username, atom_to_list(Username)}, | ||||
|         {timeline_id, atom_to_list(TimelineId)}, | ||||
|         {created, encode_datetime(Record#ts_timeline.created)}, | ||||
|         {description, Record#ts_timeline.desc}]}; | ||||
|  | ||||
| record_to_ejson(Record=#ts_entry{}) -> | ||||
|     % pull out the entry id | ||||
|     {_Username, _TimelineId, EntryId} = Record#ts_entry.ref, | ||||
|     % pull out the username, timeline id, and entry id | ||||
|     {Username, TimelineId, EntryId} = Record#ts_entry.ref, | ||||
|  | ||||
|     % convert the timestamp to a date-time | ||||
|     DateTime = calendar:gregorian_seconds_to_datetime(Record#ts_entry.timestamp), | ||||
|  | ||||
|     % create the EJSON struct | ||||
|     {struct, [ | ||||
|         {id, EntryId}, | ||||
|         {username, Username}, | ||||
|         {timeline_id, TimelineId}, | ||||
|         {entry_id, EntryId}, | ||||
|         {timestamp, encode_datetime(DateTime)}, | ||||
|         {mark, Record#ts_entry.mark}, | ||||
|         {notes, Record#ts_entry.notes}]}. | ||||
| @@ -33,21 +43,40 @@ encode_datetime({{Year, Month, Day}, {Hour, Minute, Second}}) -> | ||||
|     lists:flatten(io_lib:format("~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0BZ",  | ||||
|         [Year, Month, Day, Hour, Minute, Second])). | ||||
|  | ||||
| ejson_to_record(Empty=#ts_timeline{}, EJSON) -> | ||||
| ejson_to_record(_Empty=#ts_user{}, EJSON) -> | ||||
|     {struct, Fields} = EJSON, | ||||
|     {Username, _} = Empty#ts_timeline.ref, | ||||
|      | ||||
|     Pwd = case lists:keyfind(password, 1, Fields) of | ||||
|         false -> uninit; Field -> element(2, Field) end, | ||||
|  | ||||
|     #ts_user{ | ||||
|         username = element(2, lists:keyfind(username, 1, Fields)), | ||||
|         pwd = Pwd, | ||||
|         pwd_salt = uninit, | ||||
|         name = element(2, lists:keyfind(name, 1, Fields)), | ||||
|         email = element(2, lists:keyfind(email, 1, Fields)), | ||||
|         join_date = decode_datetime( | ||||
|             element(2, lists:keyfind(join_date, 1, Fields)))}; | ||||
|  | ||||
| ejson_to_record(_Empty=#ts_timeline{}, EJSON) -> | ||||
|     % The JSON records do not have username information | ||||
|     {struct, Fields} = EJSON, | ||||
|     Username = element(2, lists:keyfind(username, 1, Fields)), | ||||
|     TimelineId = element(2, lists:keyfind(timeline_id, 1, Fields)), | ||||
|  | ||||
|     #ts_timeline{ | ||||
|         ref = {Username, element(2, lists:keyfind(id, 1, Fields))}, | ||||
|         ref = {Username, TimelineId}, | ||||
|         created = decode_datetime(element(2, lists:keyfind(created, 1, Fields))), | ||||
|         desc = element(2, lists:keyfind(description, 1, Fields))}; | ||||
|  | ||||
| ejson_to_record(Empty=#ts_entry{}, EJSON) -> | ||||
| ejson_to_record(_Empty=#ts_entry{}, EJSON) -> | ||||
|     {struct, Fields} = EJSON, | ||||
|     {Username, TimelineId, _} = Empty#ts_entry.ref, | ||||
|     Username = element(2, lists:keyfind(username, 1, Fields)), | ||||
|     TimelineId = element(2, lists:keyfind(timeline_id, 1, Fields)), | ||||
|     EntryId = element(2, lists:keyfind(entry_id, 1, Fields)), | ||||
|  | ||||
|     #ts_entry{ | ||||
|         ref = {Username, TimelineId, element(2, lists:keyfind(id, 1, Fields))}, | ||||
|         ref = {Username, TimelineId, EntryId}, | ||||
|         timestamp = calendar:datetime_to_gregorian_seconds(decode_datetime( | ||||
|             element(2, lists:keyfind(timestamp, 1, Fields)))), | ||||
|         mark = element(2, lists:keyfind(mark, 1, Fields)), | ||||
|   | ||||
| @@ -9,22 +9,10 @@ create_table(TableOpts) -> | ||||
|         TableOpts ++ [{attributes, record_info(fields, ts_timeline)}, | ||||
|                       {type, ordered_set}]). | ||||
|  | ||||
| new(TR = #ts_timeline{}) -> | ||||
|     case mnesia:dirty_read(ts_timeline, TR#ts_timeline.ref) of | ||||
|         [ExistingRecord] -> {error, {record_exists, ExistingRecord}}; | ||||
|         [] -> mnesia:dirty_write(TR) | ||||
|     end. | ||||
| new(TR = #ts_timeline{}) -> ts_common:new(TR). | ||||
|  | ||||
| update(TR = #ts_timeline{}) -> | ||||
| update(TR = #ts_timeline{}) -> ts_commone:update(TR). | ||||
|      | ||||
|     % look for the existing record | ||||
|     case mnesia:dirty_read(ts_timeline, TR#ts_timeline.ref) of | ||||
|         % record does not exist | ||||
|         [] -> no_record; | ||||
|         % record exists, update | ||||
|         [_Record] -> mnesia:dirty_write(TR) | ||||
|     end. | ||||
|  | ||||
| lookup(Username, TimelineId) -> | ||||
|     case mnesia:dirty_read(ts_timeline, {Username, TimelineId}) of | ||||
|         [] -> no_record; | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| -module(ts_user). | ||||
| -export([]). | ||||
| -export([create_table/1, new/1, update/1, lookup/1, list/2]). | ||||
|  | ||||
| -include("ts_db_records.hrl"). | ||||
| -include_lib("stdlib/include/qlc.hrl"). | ||||
| @@ -9,4 +9,38 @@ create_table(TableOpts) -> | ||||
|         TableOpts ++ [{attributes, record_info(fields, ts_user)}, | ||||
|                       {type, ordered_set}]). | ||||
|  | ||||
| %new(TR = #ts_user | ||||
| new(UR = #ts_user{}) ->  | ||||
|     case mnesia:dirty_read(ts_user, UR#ts_user.username) of | ||||
|         [ExistingRecord] -> {error, {record_exists, ExistingRecord}}; | ||||
|         [] -> mnesia:dirty_write(hash_input_record(UR)) | ||||
|     end. | ||||
|  | ||||
| update(UR = #ts_user{}) ->  | ||||
|     case mnesia:dirty_read(ts_user, UR#ts_user.username) of | ||||
|         [] -> no_record; | ||||
|         [_Record] -> mnesia:dirty_write(hash_input_record(UR)) | ||||
|     end. | ||||
|  | ||||
| lookup(Username) -> | ||||
|     case mnesia:dirty_read(ts_user, Username) of | ||||
|         [] -> no_record; | ||||
|         [User] -> User | ||||
|     end.     | ||||
|  | ||||
| list(Start, Length) -> ts_common:list(ts_user, Start, Length). | ||||
|  | ||||
| hash_input_record(User=#ts_user{}) -> | ||||
|     % generate the password salt | ||||
|     Salt = generate_salt(), | ||||
|     % hash the password | ||||
|     HashedPwd = hash_pwd(User#ts_user.username, Salt), | ||||
|     % create a new User record | ||||
|     User#ts_user{pwd = HashedPwd, pwd_salt = Salt}. | ||||
|      | ||||
| generate_salt() -> | ||||
|     "This is a worthless salt value only suitable for testing.". | ||||
|  | ||||
| hash_pwd(Password, Salt) -> do_hash(Password ++ Salt, []). | ||||
|  | ||||
| do_hash([], Hashed) -> Hashed; | ||||
| do_hash([Char|Pwd], Hashed) -> do_hash(Pwd, [Char + 13 | Hashed]). | ||||
|   | ||||
		Reference in New Issue
	
	Block a user