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:
parent
6fe9184c8e
commit
1575e25898
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1,3 @@
|
|||||||
*.sw?
|
*.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 ====== %
|
% ======== 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.
|
delete_user(YArg, Username) -> todo.
|
||||||
|
|
||||||
@ -98,13 +137,7 @@ get_timeline(YArg, Username, TimelineId) ->
|
|||||||
|
|
||||||
no_record -> make_json_404(YArg);
|
no_record -> make_json_404(YArg);
|
||||||
|
|
||||||
Timeline ->
|
Timeline -> make_json_200(YArg, Timeline)
|
||||||
EJSONTimeline = ts_json:record_to_ejson(Timeline),
|
|
||||||
JSONReturn = json:encode({struct, [
|
|
||||||
{status, "ok"},
|
|
||||||
{timeline, EJSONTimeline}
|
|
||||||
]}),
|
|
||||||
{content, "application/json", JSONReturn}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
put_timeline(YArg, Username, TimelineId) ->
|
put_timeline(YArg, Username, TimelineId) ->
|
||||||
@ -119,16 +152,7 @@ put_timeline(YArg, Username, TimelineId) ->
|
|||||||
% insert into the database
|
% insert into the database
|
||||||
case ts_timeline:new(NewRecord) of
|
case ts_timeline:new(NewRecord) of
|
||||||
% record created
|
% record created
|
||||||
ok ->
|
ok -> [{status, 201}, make_json_200(YArg, NewRecord)];
|
||||||
|
|
||||||
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}];
|
|
||||||
|
|
||||||
% will not create, record exists
|
% will not create, record exists
|
||||||
{error, {record_exists, ExistingRecord}} ->
|
{error, {record_exists, ExistingRecord}} ->
|
||||||
@ -154,18 +178,10 @@ post_timeline(YArg, Username, TimelineId) ->
|
|||||||
NewRecord = ts_json:ejson_to_record(EmptyTimeline, EJSON),
|
NewRecord = ts_json:ejson_to_record(EmptyTimeline, EJSON),
|
||||||
|
|
||||||
case ts_timeline:update(NewRecord) of
|
case ts_timeline:update(NewRecord) of
|
||||||
ok ->
|
ok -> make_json_200(YArg, NewRecord);
|
||||||
|
|
||||||
EJSONRec = ts_json:record_to_ejson(NewRecord),
|
no_record -> make_json_404(YArg,
|
||||||
JSONReturn = json:encode({struct, [
|
[{status, "no_record"}, {see_docs, "/ts_api_doc/timeline#PSOT"}]);
|
||||||
{status, "updated"},
|
|
||||||
{timeline, EJSONRec}
|
|
||||||
]}),
|
|
||||||
|
|
||||||
{content, "application/json", JSONReturn};
|
|
||||||
|
|
||||||
no_record -> make_json_404(YArg, [{status, "no_record"},
|
|
||||||
{see_docs, "/ts_api_doc/timeline#PSOT"}]);
|
|
||||||
|
|
||||||
_Error -> make_json_500(YArg)
|
_Error -> make_json_500(YArg)
|
||||||
end.
|
end.
|
||||||
@ -194,9 +210,7 @@ list_entries(YArg, Username, Timeline) ->
|
|||||||
Entries = case SortOrder of
|
Entries = case SortOrder of
|
||||||
asc -> ts_entry:list_asc({Username, Timeline}, Start, Length);
|
asc -> ts_entry:list_asc({Username, Timeline}, Start, Length);
|
||||||
desc -> ts_entry:list_desc({Username, Timeline}, Start, Length)
|
desc -> ts_entry:list_desc({Username, Timeline}, Start, Length)
|
||||||
end
|
end.
|
||||||
|
|
||||||
.
|
|
||||||
|
|
||||||
get_entry_by_id(YArg, Username, Timeline, EventId) ->
|
get_entry_by_id(YArg, Username, Timeline, EventId) ->
|
||||||
case ts_entry:lookup(Username, Timeline, EventId) of
|
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);
|
no_record -> make_json_404(YArg);
|
||||||
|
|
||||||
% record found
|
% record found
|
||||||
Entry ->
|
Entry -> make_json_200(YArg, 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}
|
|
||||||
end.
|
end.
|
||||||
|
|
||||||
% ============================== %
|
% ============================== %
|
||||||
@ -226,6 +230,16 @@ get_entry_by_id(YArg, Username, Timeline, EventId) ->
|
|||||||
path_element_to_atom(PE) ->
|
path_element_to_atom(PE) ->
|
||||||
list_to_atom(re:replace(PE, "\\s", "_", [{return, list}])).
|
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.
|
%% Create a JSON 404 response.
|
||||||
make_json_404(YArg) -> make_json_404(YArg, []).
|
make_json_404(YArg) -> make_json_404(YArg, []).
|
||||||
make_json_404(YArg, Fields) ->
|
make_json_404(YArg, Fields) ->
|
||||||
|
@ -1,8 +1,33 @@
|
|||||||
-module(ts_common).
|
-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").
|
-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 <Length> number of records, skipping the first <Start>
|
||||||
list(Table, Start, Length)
|
list(Table, Start, Length)
|
||||||
when is_atom(Table) and is_integer(Start) and is_integer(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, '_'}, _='_'},
|
MatchHead = #ts_entry{ref = {Username, Timeline, '_'}, _='_'},
|
||||||
|
|
||||||
% select all records that match
|
% select all records that match
|
||||||
mnesia:select(ts_entry, [{MatchHead, [], ['$_']}]
|
mnesia:select(ts_entry, [{MatchHead, [], ['$_']}])
|
||||||
end),
|
end),
|
||||||
|
|
||||||
% sort
|
% sort
|
||||||
@ -69,7 +69,7 @@ list({Username, Timeline}, StartDateTime, EndDateTime, OrderFun) ->
|
|||||||
EndGuard = {'<', '$1', EndSeconds},
|
EndGuard = {'<', '$1', EndSeconds},
|
||||||
|
|
||||||
mnesia:select(ts_entry, [{MatchHead, [StartGuard, EndGuard], ['$_']}])
|
mnesia:select(ts_entry, [{MatchHead, [StartGuard, EndGuard], ['$_']}])
|
||||||
end,
|
end),
|
||||||
|
|
||||||
% sort
|
% sort
|
||||||
lists:sort(OrderFun, Entries).
|
lists:sort(OrderFun, Entries).
|
||||||
|
@ -5,26 +5,36 @@
|
|||||||
|
|
||||||
encode_record(Record) -> lists:flatten(json:encode(record_to_ejson(Record))).
|
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{}) ->
|
record_to_ejson(Record=#ts_timeline{}) ->
|
||||||
% pull out the timeline id
|
% pull out the username and timeline id
|
||||||
{_Username, TimelineId} = Record#ts_timeline.ref,
|
{Username, TimelineId} = Record#ts_timeline.ref,
|
||||||
|
|
||||||
% create the EJSON struct
|
% create the EJSON struct
|
||||||
{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)},
|
{created, encode_datetime(Record#ts_timeline.created)},
|
||||||
{description, Record#ts_timeline.desc}]};
|
{description, Record#ts_timeline.desc}]};
|
||||||
|
|
||||||
record_to_ejson(Record=#ts_entry{}) ->
|
record_to_ejson(Record=#ts_entry{}) ->
|
||||||
% pull out the entry id
|
% pull out the username, timeline id, and entry id
|
||||||
{_Username, _TimelineId, EntryId} = Record#ts_entry.ref,
|
{Username, TimelineId, EntryId} = Record#ts_entry.ref,
|
||||||
|
|
||||||
% convert the timestamp to a date-time
|
% convert the timestamp to a date-time
|
||||||
DateTime = calendar:gregorian_seconds_to_datetime(Record#ts_entry.timestamp),
|
DateTime = calendar:gregorian_seconds_to_datetime(Record#ts_entry.timestamp),
|
||||||
|
|
||||||
% create the EJSON struct
|
% create the EJSON struct
|
||||||
{struct, [
|
{struct, [
|
||||||
{id, EntryId},
|
{username, Username},
|
||||||
|
{timeline_id, TimelineId},
|
||||||
|
{entry_id, EntryId},
|
||||||
{timestamp, encode_datetime(DateTime)},
|
{timestamp, encode_datetime(DateTime)},
|
||||||
{mark, Record#ts_entry.mark},
|
{mark, Record#ts_entry.mark},
|
||||||
{notes, Record#ts_entry.notes}]}.
|
{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",
|
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])).
|
[Year, Month, Day, Hour, Minute, Second])).
|
||||||
|
|
||||||
ejson_to_record(Empty=#ts_timeline{}, EJSON) ->
|
ejson_to_record(_Empty=#ts_user{}, EJSON) ->
|
||||||
{struct, Fields} = 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{
|
#ts_timeline{
|
||||||
ref = {Username, element(2, lists:keyfind(id, 1, Fields))},
|
ref = {Username, TimelineId},
|
||||||
created = decode_datetime(element(2, lists:keyfind(created, 1, Fields))),
|
created = decode_datetime(element(2, lists:keyfind(created, 1, Fields))),
|
||||||
desc = element(2, lists:keyfind(description, 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,
|
{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{
|
#ts_entry{
|
||||||
ref = {Username, TimelineId, element(2, lists:keyfind(id, 1, Fields))},
|
ref = {Username, TimelineId, EntryId},
|
||||||
timestamp = calendar:datetime_to_gregorian_seconds(decode_datetime(
|
timestamp = calendar:datetime_to_gregorian_seconds(decode_datetime(
|
||||||
element(2, lists:keyfind(timestamp, 1, Fields)))),
|
element(2, lists:keyfind(timestamp, 1, Fields)))),
|
||||||
mark = element(2, lists:keyfind(mark, 1, Fields)),
|
mark = element(2, lists:keyfind(mark, 1, Fields)),
|
||||||
|
@ -9,22 +9,10 @@ create_table(TableOpts) ->
|
|||||||
TableOpts ++ [{attributes, record_info(fields, ts_timeline)},
|
TableOpts ++ [{attributes, record_info(fields, ts_timeline)},
|
||||||
{type, ordered_set}]).
|
{type, ordered_set}]).
|
||||||
|
|
||||||
new(TR = #ts_timeline{}) ->
|
new(TR = #ts_timeline{}) -> ts_common:new(TR).
|
||||||
case mnesia:dirty_read(ts_timeline, TR#ts_timeline.ref) of
|
|
||||||
[ExistingRecord] -> {error, {record_exists, ExistingRecord}};
|
|
||||||
[] -> mnesia:dirty_write(TR)
|
|
||||||
end.
|
|
||||||
|
|
||||||
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) ->
|
lookup(Username, TimelineId) ->
|
||||||
case mnesia:dirty_read(ts_timeline, {Username, TimelineId}) of
|
case mnesia:dirty_read(ts_timeline, {Username, TimelineId}) of
|
||||||
[] -> no_record;
|
[] -> no_record;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
-module(ts_user).
|
-module(ts_user).
|
||||||
-export([]).
|
-export([create_table/1, new/1, update/1, lookup/1, list/2]).
|
||||||
|
|
||||||
-include("ts_db_records.hrl").
|
-include("ts_db_records.hrl").
|
||||||
-include_lib("stdlib/include/qlc.hrl").
|
-include_lib("stdlib/include/qlc.hrl").
|
||||||
@ -9,4 +9,38 @@ create_table(TableOpts) ->
|
|||||||
TableOpts ++ [{attributes, record_info(fields, ts_user)},
|
TableOpts ++ [{attributes, record_info(fields, ts_user)},
|
||||||
{type, ordered_set}]).
|
{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]).
|
||||||
|
Loading…
x
Reference in New Issue
Block a user