From b5eadd6fc42cdc1bd4823e0be25394519c985a07 Mon Sep 17 00:00:00 2001 From: Jonathan Bernard Date: Tue, 7 Jun 2011 20:47:23 -0500 Subject: [PATCH] Fixed timeline creation, API logical fixes. * Resolved issues: * #0006: Fix timeline menu UI. * #0015: Create new timeline button in timeline menu. * #0017: Implement timeline creation. * Removed ts_api:post_timeline/3, corresponded to `POST` to `/ts_api/timelines/`, which makes no sense as there is no way to auto-generate new timeline-ids. * Changed ts_api:put_timeline/3 to use ts_timeline:write. This resolved #0017. * Changed ts_api:put_entry/4 to use ts_entry:write. * Implemented ts_entry:write/1 and ts_timeline:write/1. These functions are simple passthroughs to mnesia:dirty_write/1. * Added clarifing documentation for ts_json:record_to_ejson JSON formats. * Renamed ts_json:ejson_to_record/2 to ts_json:ejson_to_record_strict/2 and added ts_json:ejson_to_record/2 as a lax version of the same. * Updated all ts_api functions to use updated ts_json methods. --- .../desktop/{0006bn2.rst => 0006bs2.rst} | 2 +- .../desktop/{0015tn2.rst => 0015ts2.rst} | 2 +- .../desktop/{0017tn2.rst => 0017ts2.rst} | 4 +- doc/issues/desktop/0018bn2.rst | 9 ++ src/ts_api.erl | 86 +++---------------- src/ts_entry.erl | 13 +-- src/ts_json.erl | 74 ++++++++++++++-- src/ts_timeline.erl | 4 +- 8 files changed, 98 insertions(+), 96 deletions(-) rename doc/issues/desktop/{0006bn2.rst => 0006bs2.rst} (86%) rename doc/issues/desktop/{0015tn2.rst => 0015ts2.rst} (90%) rename doc/issues/desktop/{0017tn2.rst => 0017ts2.rst} (69%) create mode 100644 doc/issues/desktop/0018bn2.rst diff --git a/doc/issues/desktop/0006bn2.rst b/doc/issues/desktop/0006bs2.rst similarity index 86% rename from doc/issues/desktop/0006bn2.rst rename to doc/issues/desktop/0006bs2.rst index ea3ec5c..3c173e4 100644 --- a/doc/issues/desktop/0006bn2.rst +++ b/doc/issues/desktop/0006bs2.rst @@ -5,5 +5,5 @@ UI for timeline menu does not work. ========= ========== Created: 2011-05-15 -Resolved: YYYY-MM-DD +Resolved: 2011-06-07 ========= ========== \ No newline at end of file diff --git a/doc/issues/desktop/0015tn2.rst b/doc/issues/desktop/0015ts2.rst similarity index 90% rename from doc/issues/desktop/0015tn2.rst rename to doc/issues/desktop/0015ts2.rst index 4d10b0b..8c20bb0 100644 --- a/doc/issues/desktop/0015tn2.rst +++ b/doc/issues/desktop/0015ts2.rst @@ -5,5 +5,5 @@ Need a button to trigger the new timeline dialog. ========= ========== Created: 2011-06-01 -Resolved: YYYY-MM-DD +Resolved: 2011-06-07 ========= ========== \ No newline at end of file diff --git a/doc/issues/desktop/0017tn2.rst b/doc/issues/desktop/0017ts2.rst similarity index 69% rename from doc/issues/desktop/0017tn2.rst rename to doc/issues/desktop/0017ts2.rst index a960fe3..1850471 100644 --- a/doc/issues/desktop/0017tn2.rst +++ b/doc/issues/desktop/0017ts2.rst @@ -2,6 +2,6 @@ Implement timeline creation. ============================ ========= ========== -Created: 2011-05-15 -Resolved: YYYY-MM-DD +Created: 2011-06-01 +Resolved: 2011-06-07 ========= ========== \ No newline at end of file diff --git a/doc/issues/desktop/0018bn2.rst b/doc/issues/desktop/0018bn2.rst new file mode 100644 index 0000000..ba7e991 --- /dev/null +++ b/doc/issues/desktop/0018bn2.rst @@ -0,0 +1,9 @@ +Error in UI when creating or selecting a timeline. +================================================== + +Brief description. + +========= ========== +Created: 2011-06-07 +Resolved: YYYY-MM-DD +========= ========== \ No newline at end of file diff --git a/src/ts_api.erl b/src/ts_api.erl index fcac9ef..e00d20e 100644 --- a/src/ts_api.erl +++ b/src/ts_api.erl @@ -116,7 +116,6 @@ dispatch_timeline(YArg, [Username, TimelineId]) -> case HTTPMethod of 'GET' -> get_timeline(YArg, Username, TimelineId); - 'POST' -> post_timeline(YArg, Username, TimelineId); 'PUT' -> put_timeline(YArg, Username, TimelineId); 'DELETE' -> delete_timeline(YArg, Username, TimelineId); _Other -> make_json_405(YArg, [{see_docs, "/ts_api_doc/timelines.html"}]) @@ -263,70 +262,21 @@ get_timeline(YArg, Username, TimelineId) -> Timeline -> make_json_200(YArg, Timeline) end. -post_timeline(YArg, Username, TimelineId) -> - - % parse the request body - EJSON = parse_json_body(YArg), - - % parse into a Timeline record - TR = try ts_json:ejson_to_record(#ts_timeline{}, EJSON) - catch _:InputError -> - error_logger:error_report("Bad input: ~p", [InputError]), - throw(make_json_400(YArg)) - end, - - % set username and timeline id - NewRecord = TR#ts_timeline{ref = {Username, TimelineId}}, - - % insert into the database - case ts_timeline:new(NewRecord) of - % record created - ok -> [{status, 201}, make_json_200(YArg, NewRecord)]; - - % will not create, record exists - {error, {record_exists, ExistingRecord}} -> - - JSONResponse = json:encode(ts_json:record_to_ejson(ExistingRecord)), - - {content, "application/json", JSONResponse}; - - Error -> - error_logger:error_report("Unable to create a new timeline: ~p", [Error]), - make_json_500(YArg, Error) - end. - put_timeline(YArg, Username, TimelineId) -> % parse the POST data EJSON = parse_json_body(YArg), %{struct, Fields} = EJSON, % parse into a timeline record - TR = try ts_json:ejson_to_record(#ts_timeline{}, EJSON) + TR = try ts_json:ejson_to_record_strict( + #ts_timeline{ref={Username, TimelineId}}, EJSON) catch _:InputError -> error_logger:error_report("Bad input: ~p", [InputError]), throw(make_json_400(YArg)) end, - % not supported right now, would require changing all the entry keys - % check to see if they are changing the timeline id - %NewTimelineId = case lists:keyfind(1, timeline_id, Fields) of - % {timeline_id, Field} -> Field; - % false -> TimelineId end, - - % set username and timeline id - NewRecord = TR#ts_timeline{ref = {Username, TimelineId}}, - - case ts_timeline:update(NewRecord) of - ok -> make_json_200(YArg, NewRecord); - - no_record -> make_json_404(YArg, - [{error, "no such timeline"}, - {see_docs, "/ts_api_doc/timelines.html#PUT"}]); - - Error -> - error_logger:error_report("Unable update timeline: ~p", [Error]), - make_json_500(YArg, Error) - end. + ts_timeline:write(TR), + make_json_200(YArg, TR). delete_timeline(_YArg, _Username, _TimelineId) -> {status, 405}. @@ -424,16 +374,14 @@ post_entry(YArg, Username, TimelineId) -> EJSON = parse_json_body(YArg), % parse into ts_entry record - ER = try ts_json:ejson_to_record(#ts_entry{}, EJSON) + ER = try ts_json:ejson_to_record_strict( + #ts_entry{ref = {Username, TimelineId, undefined}}, EJSON) catch _:InputError -> error_logger:error_report("Bad input: ~p", [InputError]), throw(make_json_400(YArg)) end, - % set username and timeline id - NewRecord = ER#ts_entry{ref = {Username, TimelineId, undef}}, - - case ts_entry:new(NewRecord) of + case ts_entry:new(ER) of % record created {ok, CreatedRecord} -> [{status, 201}, make_json_200(YArg, CreatedRecord)]; @@ -455,25 +403,15 @@ put_entry(YArg, Username, TimelineId, EntryId) -> EJSON = parse_json_body(YArg), % parse into ts_entry record - ER = try ts_json:ejson_to_record(#ts_entry{}, EJSON) + ER = try ts_json:ejson_to_record_strict( + #ts_entry{ref={Username, TimelineId, EntryId}}, EJSON) catch _:InputError -> error_logger:error_report("Bad input: ~p", [InputError]), throw(make_json_400(YArg)) end, - % set uername, timeline id, and entry id - NewRecord = ER#ts_entry{ref = {Username, TimelineId, EntryId}}, - - case ts_entry:update(NewRecord) of - ok -> make_json_200(YArg, NewRecord); - - no_record -> make_json_404(YArg, - [{error, "no such entry"}, {see_docs, "/ts_api_doc/entries.html#POST"}]); - - Error -> - error_logger:error_report("TimeStamper: Unable to update entry: ~p", [Error]), - make_json_500(YArg, Error) - end. + ts_entry:write(ER), + make_json_200(YArg, ER). delete_entry(YArg, Username, TimelineId, EntryId) -> @@ -544,7 +482,7 @@ make_json_404(YArg, Fields) -> [{status, 404}, {content, "application/json", json:encode({struct, F2})}]. make_json_405(YArg) -> make_json_405(YArg, []). -make_json_405(YArg, Fields) -> +make_json_405(_YArg, Fields) -> % add default status if not provided F1 = case lists:keyfind(status, 1, Fields) of false -> Fields ++ [{status, "method not allowed"}]; diff --git a/src/ts_entry.erl b/src/ts_entry.erl index 8fe9072..13eeed8 100644 --- a/src/ts_entry.erl +++ b/src/ts_entry.erl @@ -1,5 +1,5 @@ -module(ts_entry). --export([create_table/1, new/1, update/1, delete/1, lookup/3, list_asc/3, list_desc/3]). +-export([create_table/1, new/1, update/1, write/1, delete/1, lookup/3, list_asc/3, list_desc/3]). -include("ts_db_records.hrl"). -include_lib("stdlib/include/qlc.hrl"). @@ -18,16 +18,9 @@ new(ER = #ts_entry{}) -> NewRow end), {ok, NewRow}. -update(ER = #ts_entry{}) -> +update(ER = #ts_entry{}) -> ts_common:update(ER). - % look for existing record - case mnesia:dirty_read(ts_entry, ER#ts_entry.ref) of - % record does not exist - [] -> no_record; - % record exists, update it - [ExistingRecord] -> - mnesia:dirty_write(ts_common:update_record(ExistingRecord, ER)) - end. +write(ER = #ts_entry{}) -> mnesia:dirty_write(ER). lookup(Username, TimelineId, EntryId) -> case mnesia:dirty_read(ts_entry, {Username, TimelineId, EntryId}) of diff --git a/src/ts_json.erl b/src/ts_json.erl index 4079353..9a6a942 100644 --- a/src/ts_json.erl +++ b/src/ts_json.erl @@ -1,10 +1,17 @@ -module(ts_json). --export([encode_record/1, record_to_ejson/1, ejson_to_record/2]). +-export([encode_record/1, record_to_ejson/1, ejson_to_record/2, ejson_to_record_strict/2]). -include("ts_db_records.hrl"). encode_record(Record) -> lists:flatten(json:encode(record_to_ejson(Record))). +% User JSON record structure: +% {"id": "john_doe", +% "name": "John Doe", +% "email": "john.doe@example.com", +% "join_date": "2011-01-01T12:00.000Z", +% "ext_data": {"last_timeline", "personal"}} + record_to_ejson(Record=#ts_user{}) -> {struct, [ {id, Record#ts_user.username}, @@ -13,6 +20,12 @@ record_to_ejson(Record=#ts_user{}) -> {join_date, encode_datetime(Record#ts_user.join_date)}, {ext_data, Record#ts_user.ext_data}]}; +% Timeline JSON record stucture: +% {"user_id": "john_doe", +% "id": "personal", +% "created": "2011-01-01T14:00.000Z", +% "description:"Personal time-tracking."} + record_to_ejson(Record=#ts_timeline{}) -> % pull out the username and timeline id {Username, TimelineId} = Record#ts_timeline.ref, @@ -24,6 +37,14 @@ record_to_ejson(Record=#ts_timeline{}) -> {created, encode_datetime(Record#ts_timeline.created)}, {description, Record#ts_timeline.desc}]}; +% Entry JSON record structure: +% {"user_id": "john_doe", +% "timeline_id": "personal", +% "id": "1", +% "timestamp": "2011-01-01T14:01.000Z", +% "mark": "Workout.", +% "notes": "First workout after a long break."} + record_to_ejson(Record=#ts_entry{}) -> % pull out the username, timeline id, and entry id {Username, TimelineId, EntryId} = Record#ts_entry.ref, @@ -44,19 +65,58 @@ 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.0B.~3.10.0BZ", [Year, Month, Day, Hour, Minute, Second, 000])). -ejson_to_record(_Empty=#ts_timeline{}, EJSON) -> +ejson_to_record(Rec=#ts_timeline{}, EJSON) -> {struct, Fields} = EJSON, - #ts_timeline{ - ref = {undefined, undefined}, + Created = case lists:keyfind(created, 1, Fields) of + false -> undefined; + {created, CreatedVal} -> decode_datetime(CreatedVal) + end, + + Desc = case lists:keyfind(description, 1, Fields) of + false -> undefined; + {description, DescVal} -> DescVal + end, + + Rec#ts_timeline{ + created = Created, + desc = Desc }; + +ejson_to_record(Rec=#ts_entry{}, EJSON) -> + {struct, Fields} = EJSON, + + Timestamp = case lists:keyfind(timestamp, 1, Fields) of + false -> undefined; + {timestamp, TSVal} -> calendar:datetime_to_gregorian_seconds( + decode_datetime(TSVal)) + end, + + Mark = case lists:keyfind(mark, 1, Fields) of + false -> undefined; + {mark, MarkVal} -> MarkVal + end, + + Notes = case lists:keyfind(notes, 1, Fields) of + false -> undefined; + {notes, NotesVal} -> NotesVal + end, + + Rec#ts_entry{ + timestamp = Timestamp, + mark = Mark, + notes = Notes}. + +ejson_to_record_strict(Rec=#ts_timeline{}, EJSON) -> + {struct, Fields} = EJSON, + + Rec#ts_timeline{ 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_strict(Rec=#ts_entry{}, EJSON) -> {struct, Fields} = EJSON, - #ts_entry{ - ref = {undefined, undefined, undefined}, + Rec#ts_entry{ timestamp = calendar:datetime_to_gregorian_seconds(decode_datetime( element(2, lists:keyfind(timestamp, 1, Fields)))), mark = element(2, lists:keyfind(mark, 1, Fields)), diff --git a/src/ts_timeline.erl b/src/ts_timeline.erl index cef41a4..547c4d0 100644 --- a/src/ts_timeline.erl +++ b/src/ts_timeline.erl @@ -1,5 +1,5 @@ -module(ts_timeline). --export([create_table/1, new/1, update/1, lookup/2, list/3]). +-export([create_table/1, new/1, update/1, write/1, lookup/2, list/3]). -include("ts_db_records.hrl"). -include_lib("stdlib/include/qlc.hrl"). @@ -12,6 +12,8 @@ create_table(TableOpts) -> new(TR = #ts_timeline{}) -> ts_common:new(TR). update(TR = #ts_timeline{}) -> ts_common:update(TR). + +write(TR = #ts_timeline{}) -> mnesia:dirty_write(TR). lookup(Username, TimelineId) -> case mnesia:dirty_read(ts_timeline, {Username, TimelineId}) of