Created timestamper module to start the application. Added cookie-based authentication to ts_api. Added utility methods to ts_api: * make_json_400/1 and make_json_400/1 * make_json_401/1 and make_json_401/2 * parse_json_body/1 reads a JSON object from a HTTP request body. Implemented ts_api_session module to manage api user sessions. Fixed ts_entry:list* methods to be 0-indexed. Removed the ts_json:ejson_to_record/1 implementation for ts_user records. Decided that ts_user records are never trusted from the client, manipulation of fields such as pwd, username will be restricted to app pages. Changed the password hashing algorithm. Now uses SHA1(pwd + 256bit salt). Want to use bcrypt, investingating cross-platform bcrypt implementation. Fixed yaws.conf config file.
94 lines
3.1 KiB
Erlang
94 lines
3.1 KiB
Erlang
-module(ts_entry).
|
|
-export([create_table/1, new/1, update/1, lookup/3, list_asc/3, list_desc/3]).
|
|
|
|
-include("ts_db_records.hrl").
|
|
-include_lib("stdlib/include/qlc.hrl").
|
|
|
|
create_table(TableOpts) ->
|
|
mnesia:create_table(ts_entry,
|
|
TableOpts ++ [{attributes, record_info(fields, ts_entry)},
|
|
{type, ordered_set}, {index, [timestamp]}]).
|
|
|
|
new(ER = #ts_entry{}) ->
|
|
{atmoic, NewRow} = mnesia:transaction(fun() ->
|
|
{Username, TimelineId, _} = ER#ts_entry.ref,
|
|
NextId = id_counter:next_counter(ts_entry_id),
|
|
NewRow = ER#ts_entry{ref = {Username, TimelineId, NextId}},
|
|
ok = mnesia:write(NewRow),
|
|
NewRow end),
|
|
{ok, NewRow}.
|
|
|
|
update(ER = #ts_entry{}) ->
|
|
|
|
% 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
|
|
[_Record] -> mnesia:dirty_write(ER)
|
|
end.
|
|
|
|
lookup(Username, TimelineId, EntryId) ->
|
|
case mnesia:dirty_read(ts_entry, {Username, TimelineId, EntryId}) of
|
|
[] -> no_record;
|
|
[Entry] -> Entry
|
|
end.
|
|
|
|
list({Username, Timeline}, Start, Length, OrderFun)
|
|
when is_integer(Start) and is_integer(Length) ->
|
|
|
|
{atomic, Entries} = mnesia:transaction(fun() ->
|
|
% match the username and timeline
|
|
MatchHead = #ts_entry{ref = {Username, Timeline, '_'}, _='_'},
|
|
|
|
% select all records that match
|
|
mnesia:select(ts_entry, [{MatchHead, [], ['$_']}])
|
|
end),
|
|
|
|
% sort
|
|
SortedEntries = lists:sort(OrderFun, Entries),
|
|
|
|
% return only the range selected.
|
|
% TODO: can we do this without selecting all entries?
|
|
lists:sublist(SortedEntries, Start + 1, Length);
|
|
|
|
list({Username, Timeline}, StartDateTime, EndDateTime, OrderFun) ->
|
|
|
|
% compute the seconds from datetimes
|
|
StartSeconds = calendar:datetime_to_gregorian_seconds(StartDateTime),
|
|
EndSeconds = calendar:datetime_to_gregorian_seconds(EndDateTime),
|
|
|
|
% select all entries from the timeline that are within the time range
|
|
{atomic, Entries} = mnesia:transaction(fun() ->
|
|
|
|
% match the username and timeline id
|
|
MatchHead = #ts_entry{ref = {Username, Timeline, '_'}, timestamp='$1', _='_'},
|
|
|
|
% guards for the time range
|
|
StartGuard = {'>=', '$1', StartSeconds},
|
|
EndGuard = {'<', '$1', EndSeconds},
|
|
|
|
mnesia:select(ts_entry, [{MatchHead, [StartGuard, EndGuard], ['$_']}])
|
|
end),
|
|
|
|
% sort
|
|
lists:sort(OrderFun, Entries).
|
|
|
|
list_asc(TimelineRef, Start, Length)
|
|
when is_integer(Start) and is_integer(Length) ->
|
|
list(TimelineRef, Start, Length, fun timestamp_asc/2);
|
|
|
|
list_asc(TimelineRef, StartDateTime, EndDateTime) ->
|
|
list(TimelineRef, StartDateTime, EndDateTime, fun timestamp_asc/2).
|
|
|
|
list_desc(TimelineRef, Start, Length)
|
|
when is_integer(Start) and is_integer(Length) ->
|
|
list(TimelineRef, Start, Length, fun timestamp_desc/2);
|
|
|
|
list_desc(TimelineRef, StartDateTime, EndDateTime) ->
|
|
list(TimelineRef, StartDateTime, EndDateTime, fun timestamp_desc/2).
|
|
|
|
timestamp_asc(E1, E2) -> E1#ts_entry.timestamp > E2#ts_entry.timestamp.
|
|
|
|
timestamp_desc(E1, E2) -> E1#ts_entry.timestamp < E2#ts_entry.timestamp.
|