Fixing problems introduced by the model change.

* Many calls to ``ts_ext_data:get_properties/1`` from ``ts_api`` were passing
  the record reference, not the record itself. Fixed.
* Created ``ts_api:put_user/2`` which updates a ``ts_user`` record. This is
  needed specifically for updating the ``liat_timeline`` extended data property.
* Removed unreachable code in ``ts_api:post_entry/3``
* Fixed return value of some ``ts_user`` functions to use the
  ``{Status, Value}`` convention. TODO: make sure all calls are using the same
  convention.
* Fixed error message formatting in ``ts_ext_data:set_property/3``.
* Added clauses to ``ts_json:ejson_to_record/2``,
  ``ts_json:ejson_to_record_strict/2`` and ``ts_json:construct_record/3`` to
  handle the ``ts_user`` record type.
* Added code to ``AppView.loadInitialData`` and ``AppView.selectTimeline`` to
  support the ``last_timeline`` extended data property.
This commit is contained in:
Jonathan Bernard
2011-06-15 16:50:38 -05:00
parent 3999e736be
commit cd182d54c3
16 changed files with 297 additions and 79 deletions

View File

@ -22,6 +22,7 @@ out(YArg) ->
{'EXIT', Err} ->
% TODO: log error internally
error_logger:error_report("TimeStamper: ~p", [Err]),
io:format("Error: ~n~p", [Err]),
make_json_500(YArg, Err);
Other -> Other
end.
@ -211,7 +212,7 @@ get_user_summary(YArg, Username) ->
User ->
% get user extended data properties
UserExtData = ts_ext_data:get_properties(Username),
UserExtData = ts_ext_data:get_properties(User),
% convert to intermediate JSON form
EJSONUser = ts_json:record_to_ejson(User, UserExtData),
@ -223,7 +224,7 @@ get_user_summary(YArg, Username) ->
lists:map(
fun(Timeline) ->
ts_json:record_to_ejson(Timeline,
ts_ext_data:get_properties(Timeline#ts_timeline.ref))
ts_ext_data:get_properties(Timeline))
end,
Timelines)},
@ -246,6 +247,25 @@ get_user(YArg, Username) ->
User -> make_json_200(YArg, User)
end.
put_user(YArg, Username) ->
% parse the POST data
EJSON = parse_json_body(YArg),
{UR, ExtData} =
try ts_user:ejson_to_record_strict(#ts_user{username=Username}, EJSON)
catch throw:{InputError, StackTrace} ->
error_logger:error_report("Bad input in put_user/2: ~p",
[InputError]),
throw(make_json_400(YArg, [{request_error, InputError}]))
end,
% update the record (we do not support creating users via the API right now
{ok, UpdatedRec} = ts_user:update(UR, ExtData),
% return a 200
make_json_200(YArg, UpdatedRec).
list_timelines(YArg, Username) ->
% pull out the POST data
QueryData = yaws_api:parse_query(YArg),
@ -270,7 +290,7 @@ list_timelines(YArg, Username) ->
EJSONTimelines = {array, lists:map(
fun (Timeline) ->
ts_json:record_to_ejson(Timeline,
ts_ext_data:get_properties(Timeline#ts_timeline.ref))
ts_ext_data:get_properties(Timeline))
end,
Timelines)},
@ -301,7 +321,7 @@ put_timeline(YArg, Username, TimelineId) ->
% we can not parse it, tell the user
catch throw:{InputError, _StackTrace} ->
error_logger:error_report("Bad input: ~p", [InputError]),
throw(make_json_400(YArg, {request_error, InputError}))
throw(make_json_400(YArg, [{request_error, InputError}]))
end,
% write the changes.
@ -355,7 +375,7 @@ list_entries(YArg, Username, TimelineId) ->
EJSONEntries = {array, lists:map(
fun (Entry) ->
ts_json:record_to_ejson(Entry,
ts_ext_data:get_properties(Entry#ts_entry.ref))
ts_ext_data:get_properties(Entry))
end,
Entries)},
@ -391,7 +411,7 @@ list_entries(YArg, Username, TimelineId) ->
EJSONEntries = {array, lists:map(
fun (Entry) ->
ts_json:record_to_ejson(Entry,
ts_ext_data:get_properties(Entry#ts_entry.ref))
ts_ext_data:get_properties(Entry))
end,
Entries)},
@ -418,7 +438,7 @@ post_entry(YArg, Username, TimelineId) ->
#ts_entry{ref = {Username, TimelineId, undefined}}, EJSON)
catch _:InputError ->
error_logger:error_report("Bad input: ~p", [InputError]),
throw(make_json_400(YArg, {request_error, InputError}))
throw(make_json_400(YArg, [{request_error, InputError}]))
end,
case ts_entry:new(ER, ExtData) of
@ -427,14 +447,8 @@ post_entry(YArg, Username, TimelineId) ->
[{status, 201}, make_json_200(YArg, CreatedRecord)];
% will not create, record exists
{error, {record_exists, ExistingRecord}} ->
JSONResponse = json:encode(ts_json:record_to_ejson(ExistingRecord)),
{content, "application/json", JSONResponse};
OtherError ->
error_logger:error_report("TimeStamper: Could not create entry: ~p", [OtherError]),
error_logger:error_report("Could not create entry: ~p", [OtherError]),
make_json_500(YArg, OtherError)
end.
@ -486,8 +500,9 @@ parse_json_body(YArg) ->
%% Create a JSON 200 response.
make_json_200(_YArg, Record) ->
RecordExtData = ts_ext_data:get_properties(element(2, Record)),
JSONResponse = json:encode(ts_json:record_to_ejson(Record, RecordExtData)),
RecordExtData = ts_ext_data:get_properties(Record),
EJSON = ts_json:record_to_ejson(Record, RecordExtData),
JSONResponse = json:encode(EJSON),
{content, "application/json", JSONResponse}.
make_json_400(YArg) -> make_json_400(YArg, []).
@ -532,14 +547,14 @@ make_json_405(_YArg, Fields) ->
end,
% add the path they requested
% F2 = F1 ++ [{path, io_lib:format("~s", [(YArg#arg.req)#http_request.path])}],
% F2 = F1 ++ [{path, io_lib:format("~p", [(YArg#arg.req)#http_request.path])}],
[{status, 405}, {content, "application/json", json:encode({struct, F1})}].
make_json_500(_YArg, Error) ->
EJSON = {struct, [
{status, "internal server error"},
{error, io_lib:format("~s", [Error])}]},
{error, lists:flatten(io_lib:format("~p", [Error]))}]},
[{status, 500}, {content, "application/json", json:encode(EJSON)}].
make_json_500(_YArg) ->

View File

@ -100,10 +100,9 @@ list(Query, Start, Length) ->
% should be wrapped in a transaction
do_set_ext_data(Record, ExtData) when is_list(ExtData) ->
Ref = element(2, Record),
lists:foreach(
fun({Key, Val}) ->
{atomic, ok} = ts_ext_data:set_property(Ref, Key, Val)
{atomic, ok} = ts_ext_data:set_property(Record, Key, Val)
end,
ExtData),
ok.

View File

@ -19,7 +19,7 @@ new(ER = #ts_entry{}, ExtData) when is_list(ExtData) ->
NewRow = do_new(ER),
ts_common:do_set_ext_data(ER, ExtData),
NewRow end),
NewRow.
{ok, NewRow}.
do_new(ER = #ts_entry{}) ->
{Username, TimelineId, _} = ER#ts_entry.ref,

View File

@ -21,8 +21,8 @@ set_property(Rec=#ts_timeline{}, entry_exclusions, ExclusionList) ->
do_set_property(Rec#ts_timeline.ref, entry_exclusions, ExclusionList);
set_property(Rec, Key, _Value) ->
throw(io_lib:format("Property '~s' not available for a ~s record.",
[Key, element(1, Rec)])).
throw(lists:flatten(io_lib:format("Property '~s' not available for a ~s record.",
[Key, element(1, Rec)]))).
get_property(Ref, PropKey) ->
{atomic, Result} = mnesia:transaction(fun() ->

View File

@ -81,6 +81,16 @@ ejson_to_record(Empty, Ref, EJSON) ->
Constructed = ejson_to_record(Empty, EJSON),
setelement(2, Constructed, Ref).
ejson_to_record_strict(Empty=#ts_user{}, EJSON) ->
Constructed = ejson_to_record(Empty, EJSON),
case Constructed of
#ts_user{name = undefined} -> throw("Missing user 'name' field.");
#ts_user{email = undefined} -> throw("Missing user 'email' field.");
#ts_user{join_date = undefined} ->
throw("Missing user 'join_date' field.");
_Other -> Constructed
end;
ejson_to_record_strict(Empty=#ts_timeline{}, EJSON) ->
Constructed = ejson_to_record(Empty, EJSON),
case Constructed of
@ -107,22 +117,55 @@ ejson_to_record_strict(Empty, Ref, EJSON) ->
Constructed = ejson_to_record_strict(Empty, EJSON),
setelement(2, Constructed, Ref).
construct_record(Timeline=#ts_timeline{}, [{Key, Val}|Fields], ExtData) ->
construct_record(User=#ts_user{}, [{Key, Value}|Fields], ExtData) ->
case Key of
created -> construct_record(
Timeline#ts_timeline{created = decode_datetime(Val)},
id -> construct_record(User#ts_user{username = Value},
Fields, ExtData);
name -> construct_record(User#ts_user{name = Value}, Fields, ExtData);
join_date -> construct_record(
User#ts_user{join_date = decode_datetime(Value)},
Fields, ExtData);
_Other ->
ExtDataProp = ejson_to_ext_data({Key, Value}),
construct_record(User, Fields, [ExtDataProp|ExtData])
end;
construct_record(Timeline=#ts_timeline{}, [{Key, Value}|Fields], ExtData) ->
case Key of
user_id ->
{_, TimelineId} = Timeline#ts_timeline.ref,
construct_record(Timeline#ts_timeline{ref = {Value, TimelineId}},
Fields, ExtData);
description -> construct_record(Timeline#ts_timeline{desc = Val},
id ->
{Username, _} = Timeline#ts_timeline.ref,
construct_record(Timeline#ts_timeline{ref = {Username, Value}},
Fields, ExtData);
created -> construct_record(
Timeline#ts_timeline{created = decode_datetime(Value)},
Fields, ExtData);
description -> construct_record(Timeline#ts_timeline{desc = Value},
Fields, ExtData);
_Other ->
ExtDataProp = ejson_to_ext_data({Key, Val}),
ExtDataProp = ejson_to_ext_data({Key, Value}),
construct_record(Timeline, Fields, [ExtDataProp|ExtData])
end;
construct_record(Entry=#ts_entry{}, [{Key, Value}|Fields], ExtData) ->
case Key of
user_id ->
{_, TimelineId, EntryId} = Entry#ts_entry.ref,
construct_record(Entry#ts_entry{ref = {Value, TimelineId, EntryId}},
Fields, ExtData);
timeline_id ->
{Username, _, EntryId} = Entry#ts_entry.ref,
construct_record(Entry#ts_entry{ref = {Username, Value, EntryId}},
Fields, ExtData);
id ->
{Username, TimelineId, _} = Entry#ts_entry.ref,
construct_record(Entry#ts_entry{ref = {Username, TimelineId, Value}},
Fields, ExtData);
timestamp -> construct_record(
Entry#ts_entry{timestamp = calendar:datetime_to_gregoraian_seconds(
Entry#ts_entry{timestamp = calendar:datetime_to_gregorian_seconds(
decode_datetime(Value))},
Fields, ExtData);
mark -> construct_record(Entry#ts_entry{mark=Value}, Fields, ExtData);

View File

@ -13,31 +13,34 @@ create_table(TableOpts) ->
% expects the password in clear
new(UR = #ts_user{}) ->
{atomic, Result} = mnesia:transaction(fun() -> do_new(UR) end),
Result.
{ok, Result}.
new(UR = #ts_user{}, ExtData) ->
{atomic, Result} = mnesia:transaction(fun() ->
NewUser = do_new(UR),
ts_common:do_set_ext_data(UR, ExtData),
NewUser end),
Result.
{ok, Result}.
do_new(UR = #ts_user{}) ->
case mnesia:read(ts_user, UR#ts_user.username) of
[ExistingRecord] -> {error, {record_exists, ExistingRecord}};
[] -> mnesia:write(hash_input_record(UR))
[] ->
NewRec = hash_input_record(UR),
mnesia:write(NewRec),
NewRec
end.
update(UR = #ts_user{}) ->
{atomic, Result} = mnesia:transaction(fun() -> do_update(UR) end),
Result.
{ok, Result}.
update(UR = #ts_user{}, ExtData) ->
{atomic, Result} = mnesia:transaction(fun() ->
UpdatedUser = do_update(UR),
ts_common:do_set_ext_data(UR, ExtData),
UpdatedUser end),
Result.
{ok, Result}.
do_update(UR = #ts_user{}) ->
case mnesia:read(ts_user, UR#ts_user.username) of
@ -48,7 +51,8 @@ do_update(UR = #ts_user{}) ->
undefined -> UpdatedRecord;
_Password -> hash_input_record(UpdatedRecord)
end,
mnesia:write(HashedRecord)
mnesia:write(HashedRecord),
HashedRecord
end.
lookup(Username) ->