-- -- a data model for "to do lists" -- -- by Ben Adida (ben@mit.edu) create sequence todo_list_id_sequence; create table todo_lists ( list_id integer not null primary key, user_id integer not null references users, list_name varchar(100), list_details varchar(4000), creation_date datetime, public_p char(1) default 'f' check (public_p in ('t', 'f')), due_date datetime ); create index todo_lists_by_user_id on todo_lists(user_id); -- -- A table that says which users are allowed to assign things -- to another list for another user -- create table todo_list_user_map ( list_id integer not null references todo_lists, user_id integer not null references users, automatic_approval char(1) default 't' check (automatic_approval in ('t','f')), primary key (list_id, user_id) ); create table todo_list_user_group_map ( list_id integer not null references todo_lists, user_group_id integer not null references user_groups, primary key (list_id, user_group_id) ); create sequence todo_item_id_sequence; create table todo_items ( item_id integer not null primary key, list_id integer not null references todo_lists, -- properties priority integer check (priority between 1 and 5), creation_date datetime, due_date datetime, completion_date datetime, deletion_date datetime, -- this can be assigned by someone else assigned_by integer references users, approved_p char(1) default 't' check (approved_p in ('t','f')), item_details varchar(4000) ); create view deleted_todo_items as select * from todo_items where deletion_date is not null; create view viewable_todo_items as select * from todo_items where deletion_date is null; create view open_todo_items as select * from viewable_todo_items where completion_date is NULL; create view closed_todo_items as select * from viewable_todo_items where completion_date is NOT NULL; create view approved_todo_items as select * from viewable_todo_items where approved_p='t'; create view unapproved_todo_items as select * from viewable_todo_items where approved_p='f'; create view pending_todo_items as select * from viewable_todo_items where approved_p is NULL; -- user preferences create table todo_user_preferences ( user_id integer not null primary key references users, -- a bunch of todo preferences -- an expiration for old items old_item_expire integer default 7 not null, -- when sorting items, sort by sort_by varchar(100), -- mail notification notification_interval integer default 1 not null, -- last time user was notified last_notification datetime, -- which time zone, and when does midnight start time_offset integer, personal_midnight integer, -- separate the completed items or not separate_completed_items_p char(1) default 'f' check (separate_completed_items_p in ('t','f')) ); -- A function to tell if two dates are on same day create function same_day_p(datetime, datetime, integer) returns char as ' DECLARE first_date alias for $1; second_date alias for $2; personal_midnight alias for $3; first_day char(2); second_day char(2); difference numeric; BEGIN IF first_date is NULL or second_date is NULL THEN return ''f''; END IF; first_day:= date_part (''day'', first_date - (personal_midnight || '' hours'')::timespan); second_day:= date_part (''day'', second_date - (personal_midnight || '' hours'')::timespan); difference:= date_num_days(second_date - first_date); IF abs(difference)>1 THEN return(''f''); END IF; IF first_day != second_day THEN return(''f''); END IF; return(''t''); END; ' language 'plpgsql'; -- a table for correspondance between relative dates in pretty -- format, and what they're displayed as create table relative_date_pretty_text ( pretty_date varchar(25), min_days integer, max_days integer, days integer ); insert into relative_date_pretty_text (pretty_date, min_days, max_days, days) values ('in a month', 25, 35, 30); insert into relative_date_pretty_text (pretty_date, min_days, max_days, days) values ('in a week', 5, 9, 7); insert into relative_date_pretty_text (pretty_date, min_days, max_days, days) values ('in a few days', 2, 5, 3); insert into relative_date_pretty_text (pretty_date, min_days, max_days, days) values ('tomorrow', 1, 2, 1); insert into relative_date_pretty_text (pretty_date, min_days, max_days, days) values ('today', -1, 1, 0); -- a function to display the date in a nice way create function pretty_relative_date(datetime) returns varchar as ' DECLARE BEGIN return pretty_relative_date( $1,0); END; ' language 'plpgsql'; create function pretty_relative_date(datetime, integer) returns varchar as ' DECLARE v_date alias for $1; midnight_offset alias for $2; same_days char(1); difference_in_days numeric; v_pretty_date varchar(25); BEGIN IF v_date is NULL THEN return(NULL); END IF; difference_in_days:= date_num_days(v_date-sysdate()); same_days:= same_day_p(sysdate(), v_date, midnight_offset); IF same_days=''f'' AND difference_in_days>= -1 AND difference_in_days<0 THEN difference_in_days:= -1.5; END IF; IF same_days=''f'' AND difference_in_days>= 0 and difference_in_days<= 1 THEN difference_in_days:= 1.5; END IF; select pretty_date into v_pretty_date from relative_date_pretty_text where (min_days is NULL or min_days<= difference_in_days) AND (max_days is NULL or max_days> difference_in_days) AND days is NOT NULL; return(v_pretty_date); END; ' language 'plpgsql'; -- -- a function to get the days field from a number of days from the pretty text -- create function todo_days_from_pretty(varchar) returns numeric as ' DECLARE pretty_text alias for $1; return_num numeric; BEGIN select days into return_num from relative_date_pretty_text where pretty_date= pretty_text; return(return_num); END; ' language 'plpgsql'; -- A function to find the min in the interval create function todo_interval_min(datetime, integer) returns integer as ' DECLARE v_date alias for $1; midnight_offset alias for $2; v_return_num integer; BEGIN return(todo_days_from_pretty(pretty_relative_date(v_date, midnight_offset))); END; ' language 'plpgsql'; create function todo_interval_min(datetime) returns integer as ' DECLARE BEGIN return(todo_interval_min($1, 0)); END; ' language 'plpgsql'; -- A function to check access create function user_can_access_list_p(integer, integer) returns char as ' DECLARE v_user_id alias for $1; v_list_id alias for $2; v_count integer; v_public_p char(1); BEGIN select public_p into v_public_p from todo_lists where list_id= v_list_id; IF v_public_p = ''t'' then return(''t''); end if; select count(*) into v_count from todo_lists where user_id = v_user_id and list_id = v_list_id; IF (v_count>0) THEN return(''t''); END IF; select count(*) into v_count from todo_list_user_map where list_id= v_list_id and user_id= v_user_id; IF (v_count>0) THEN return(''t''); END IF; select count(*) into v_count from todo_list_user_group_map where list_id= v_list_id and 0<(select count(*) from user_group_map where group_id= todo_list_user_group_map.user_group_id and user_id= v_user_id); IF (v_count>0) THEN return(''t''); END IF; return(''f''); END; ' language 'plpgsql';