* ts_api:list_timelines/2 was looking for keys as atoms instead of lists (strings). * Exported the ts_json:decode_datetime/1 function (needed by ts_api module). * Fixed a crash case when trying to check the credentials of a non-existent user.
214 lines
7.7 KiB
Erlang
214 lines
7.7 KiB
Erlang
-module(ts_json).
|
|
-export([encode_record/2, record_to_ejson/2,
|
|
ejson_to_record/2, ejson_to_record/3,
|
|
ejson_to_record_strict/2, ejson_to_record_strict/3,
|
|
decode_datetime/1]).
|
|
|
|
-include("ts_db_records.hrl").
|
|
|
|
encode_record(Record, ExtData) -> lists:flatten(json:encode(record_to_ejson(Record, ExtData))).
|
|
|
|
% User JSON record required structure:
|
|
% {"id": "john_doe",
|
|
% "name": "John Doe",
|
|
% "email": "john.doe@example.com",
|
|
% "join_date": "2011-01-01T12:00.000Z"}
|
|
|
|
record_to_ejson(Record=#ts_user{}, ExtData) ->
|
|
{struct, [
|
|
{id, Record#ts_user.username},
|
|
{name, Record#ts_user.name},
|
|
{email, Record#ts_user.email},
|
|
{join_date, encode_datetime(Record#ts_user.join_date)}] ++
|
|
lists:map(fun ext_data_to_ejson/1, ExtData)};
|
|
|
|
% 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{}, ExtData) ->
|
|
% pull out the username and timeline id
|
|
{Username, TimelineId} = Record#ts_timeline.ref,
|
|
|
|
% create the EJSON struct
|
|
{struct, [
|
|
{user_id, Username},
|
|
{id, TimelineId},
|
|
{created, encode_datetime(Record#ts_timeline.created)},
|
|
{description, Record#ts_timeline.desc}] ++
|
|
lists:map(fun ext_data_to_ejson/1, ExtData)};
|
|
|
|
% 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{}, ExtData) ->
|
|
% 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, [
|
|
{user_id, Username},
|
|
{timeline_id, TimelineId},
|
|
{id, EntryId},
|
|
{timestamp, encode_datetime(DateTime)},
|
|
{mark, Record#ts_entry.mark},
|
|
{notes, Record#ts_entry.notes}] ++
|
|
lists:map(fun ext_data_to_ejson/1, ExtData)}.
|
|
|
|
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])).
|
|
|
|
ext_data_to_ejson({Key, Value}) ->
|
|
case Key of
|
|
entry_exclusions -> {Key, {array, Value}};
|
|
_Other -> {Key, Value}
|
|
end.
|
|
|
|
ejson_to_record(Empty, {struct, EJSONFields}) ->
|
|
construct_record(Empty, EJSONFields, []).
|
|
|
|
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
|
|
#ts_timeline{created = undefined} ->
|
|
throw("Missing timeline 'created' field.");
|
|
#ts_timeline{desc = undefined} ->
|
|
throw("Missing timeline 'description' field.");
|
|
_Other -> Constructed
|
|
end;
|
|
|
|
ejson_to_record_strict(Empty=#ts_entry{}, EJSON) ->
|
|
Constructed = ejson_to_record(Empty, EJSON),
|
|
case Constructed of
|
|
#ts_entry{timestamp = undefined} ->
|
|
throw("Missing timelne 'timestamp' field.");
|
|
#ts_entry{mark = undefined} ->
|
|
throw("Missing timeline 'mark' field.");
|
|
#ts_entry{notes = undefined} ->
|
|
throw("Missing timeline 'notes' field/");
|
|
_Other -> Constructed
|
|
end.
|
|
|
|
ejson_to_record_strict(Empty, Ref, EJSON) ->
|
|
Constructed = ejson_to_record_strict(Empty, EJSON),
|
|
setelement(2, Constructed, Ref).
|
|
|
|
construct_record(User=#ts_user{}, [{Key, Value}|Fields], ExtData) ->
|
|
case Key of
|
|
id -> construct_record(User#ts_user{username = Value},
|
|
Fields, ExtData);
|
|
name -> construct_record(User#ts_user{name = Value}, Fields, ExtData);
|
|
email -> construct_record(User#ts_user{email = 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);
|
|
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, 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_gregorian_seconds(
|
|
decode_datetime(Value))},
|
|
Fields, ExtData);
|
|
mark -> construct_record(Entry#ts_entry{mark=Value}, Fields, ExtData);
|
|
notes -> construct_record(Entry#ts_entry{notes=Value}, Fields, ExtData);
|
|
_Other ->
|
|
ExtDataProp = ejson_to_ext_data({Key, Value}),
|
|
construct_record(Entry, Fields, [ExtDataProp|ExtData])
|
|
end;
|
|
|
|
construct_record(Record, [], ExtData) -> {Record, ExtData}.
|
|
|
|
decode_datetime(DateTimeString) ->
|
|
% TODO: catch badmatch and badarg on whole function
|
|
|
|
[DateString, TimeString] = re:split(DateTimeString, "[TZ]",
|
|
[{return, list}, trim]),
|
|
|
|
[YearString, MonthString, DayString] =
|
|
re:split(DateString, "-", [{return, list}]),
|
|
|
|
[HourString, MinuteString, SecondString] =
|
|
case re:split(TimeString, "[:\\.]", [{return, list}]) of
|
|
[HS, MS, SS, _MSS] -> [HS, MS, SS];
|
|
[HS, MS, SS] -> [HS, MS, SS]
|
|
end,
|
|
|
|
Date = {list_to_integer(YearString), list_to_integer(MonthString),
|
|
list_to_integer(DayString)},
|
|
|
|
Time = {list_to_integer(HourString), list_to_integer(MinuteString),
|
|
list_to_integer(SecondString)},
|
|
|
|
{Date, Time}.
|
|
|
|
ejson_to_ext_data({Key, Value}) ->
|
|
case Key of
|
|
entry_exclusions ->
|
|
{array, ExclList} = Value,
|
|
{Key, ExclList};
|
|
_Other -> {Key, Value}
|
|
end.
|
|
|
|
|