--
-- data-model.sql for generic bboard system
-- bashed for Oracle8 12/3/97
-- bashed to run with community data model (users table rather
-- than unauthenticated email/name) by Tracy Adams in the summer of 1998
-- bashed 9/13/98 by philg to run with the Scorecard data model
-- edited on 11/15/98 by philg so that the usgeospatial style
-- of forum, from www.scorecard.org, would be part of the
-- generic community system
-- added active_p to bboard_topics teadams@mit.edu 1/7/98
-- edited by Tracy Adams (teadams@mit.edu) on 2/7/98 to prevent
--   multiple row inserted into msg_id_generator if this file
--   is loaded more than once
--
-- updated by hqm to use numeric sequence vals as primary keys,
-- and to integrate better with ACS user/group model 
-- see /doc/bboard-new.html 
-- hqm@ai.mit.edu 8/99

-- Copyright 1996, 1997 Philip Greenspun (philg@mit.edu)
--

create sequence bboard_topic_id_sequence;

create table bboard_topics (
	topic_id	integer not null primary key,
	-- topic name
	topic		varchar(200) unique not null,
	-- read access rights
	-- can be one of any (anonymous), public (any registered user), group
	read_access	varchar(16) default 'any' check (read_access in ('any','public','group')),
	-- write (post new message) access
	-- can be one of (public, group)
	write_access 	varchar(16) default 'public' check (write_access in ('public','group')),
 	users_can_initiate_threads_p	boolean default 't',
	backlink	varchar(4000),	-- a URL pointing back to the relevant page
	backlink_title	varchar(4000),	-- what to say for the link back
	blather		varchar(4000),	-- arbitrary HTML text that goes at the top of the page
	-- posting is always restricted to members
	-- is viewing restricted to members or only posting?
	restricted_p	boolean default 'f',
	primary_maintainer_id	integer not null references users(user_id),
	subject_line_suffix	varchar(40),	-- whether to put something after the subject line, e.g., 'name', 'date'
	notify_of_new_postings_p	char(1) default 't' check (notify_of_new_postings_p in ('t','f')),	-- send email when a message is added?
	pre_post_caveat		varchar(4000),	-- special HTML to encourage user to search elsewhere before posting a new message
	-- 'unmoderated', 'new_threads_by_maintainer', 'new_threads_by_helpers'
	-- 'all_threads_by_maintainer', 'all_threads_by_helpers','answers_only_from_helpers', 'moderated_topics'
	moderation_policy	varchar(40),
	-- used for keeping messages for 50 US states, for example
	-- where each state is a top level posting but not really a 
	-- question
	-- if this isn't NULL then we put in an "about" link
	policy_statement	varchar(4000),
	-- presentation_type  q-and-a (Question and answer format), threads (standard listserve), or ed_com (Question and response pages separated, editiorial language)
	presentation_type	varchar(20) default 'q_and_a' constraint check_presentation_type check(presentation_type in ('q_and_a','threads', 'ed_com', 'usgeospatial')),
	-- stuff just for Q&A  use
	q_and_a_sort_order	varchar(4) default 'asc' not null check (q_and_a_sort_order in ('asc','desc')),
	q_and_a_categorized_p	boolean default 'f',
	q_and_a_new_days	integer default 7,
	q_and_a_solicit_category_p	boolean default 't',
	q_and_a_cats_user_extensible_p	boolean default 'f',
	-- use the interest level system
	q_and_a_use_interest_level_p 	boolean default 't',
	-- for popular boards, only show categories for non-new msgs
	q_and_a_show_cats_only_p	boolean default 'f',
	-- for things like NE43 memory project and 6.001 pset site 
	-- top level threads can have custom sort keys, e.g., date
	-- of story (rather than date of posting)
	custom_sort_key_p 		boolean default 'f',
	custom_sort_key_name		varchar(50),	-- for display
	-- SQL data type, lowercase, e.g., "date" (ANSI format so that it sorts)
	-- we really only use this for user input validation
	custom_sort_key_type		varchar(20),
	custom_sort_order		varchar(4) default 'asc' not null check (custom_sort_order in ('asc','desc')),
	-- display to user if there aren't message yet
	custom_sort_not_found_text	varchar(4000),
	-- ask user to supply a sort key with new postings
	custom_sort_solicit_p		boolean default 'f',
	-- ask user to supply a pretty sort key for display
	-- e.g., "Fall 1997" instead of 9-29-97
	custom_sort_solicit_pretty_p   boolean default 'f',
	custom_sort_pretty_name		varchar(50),	-- for display
	custom_sort_pretty_explanation	varchar(100),	-- why we ask for it
	-- fragment of Tcl code that evaluates to 0 if a sort key is
	-- bad, 1 if OK, assumed to include "$custom_sort_key"
	custom_sort_validation_code	varchar(4000),
	-- for the 2nd round of 6.001 discussion thinking
	category_centric_p		boolean default 'f',
	-- image and file uploading
	uploads_anticipated		varchar(30) check (uploads_anticipated is null or uploads_anticipated in ('images','files','images_or_files')),
	-- should this forum come up on the user interface?
	active_p			boolean default 't',
	group_id                        integer references user_groups
);


-- useful for maintaining FAQs

create table bboard_q_and_a_categories (
	topic_id	integer not null references bboard_topics,	
	category	varchar(200) not null
);

-- useful for keeping idiots out of forums, e.g., looking for
-- "aperature" in the photo.net Q&A forum

create table bboard_bozo_patterns (
	topic_id	integer not null references bboard_topics,
	the_regexp	varchar(200) not null,
	scope		varchar(20) default 'both' 
           check(scope in ('one_line','message','both')),
        message_to_user	varchar(4000),
	creation_date	datetime not null,
	creation_user 		integer not null references users(user_id),
	creation_comment	varchar(4000),
	primary key (topic_id, the_regexp)
);

-- **** primary key using index tablespace photonet_index

create table bboard (
	msg_id		char(6) primary key,
	refers_to	char(6),
        root_msg_id	char(6),
	topic_id	integer not null references bboard_topics,
	category	varchar(200),	-- only used for categorized Q&A forums
	originating_ip	varchar(16),	-- stored as string, separated by periods
	user_id		integer not null references users,
	one_line	varchar(700),
	-- message can now be full-lengthed text field in PostreSQL v7.0
	message		lztext,
	-- html_p - is the message in html or not
	html_p		boolean default 'f',
	posting_time	datetime,
	expiration_days	integer,	-- optional N days after posting_time to expire
	-- really only used for postings that initiate threads
	interest_level	integer check ( interest_level is null or interest_level >= 0 and interest_level <= 10 ),
	sort_key	varchar(700),
	-- only used for weirdo things like NE43 memory project and 
	-- 6.001
	-- if this is a DATE, it has to be an ANSI so that it will
	-- sort lexicographically
	-- I guess we should constraint this to be UNIQUE
	custom_sort_key		varchar(100),
	custom_sort_key_pretty	varchar(100),
	-- stuff for US geospatial forums
	epa_region	integer check(epa_region is null or epa_region >= 1 and epa_region <= 10),
	usps_abbrev	char(2) references states,
	fips_county_code	char(5) references counties,
	zip_code	varchar(5),
        urgent_p        boolean not null default 'f'
);

-- for all of the following indices: **** tablespace photonet_index

create index bboard_by_user on bboard (user_id);

-- this SORT_KEY index will make fetching single Q&A thread fast
-- but it will only work if sort_key is bashed down to 758 chars
-- (note:  Illustra could trivially have indexed this)

-- DRB
-- "like" on an indexed field doesn't work in 6.5, strange, and was
-- fixed in 6.5.1 by forcing a sequential scan.  I've added a root_msg_id
-- field and am indexing it to get around the problem.

-- create index bboard_by_sort_key on bboard ( sort_key );
create index bboard_by_root_msg_id on bboard ( root_msg_id );

-- we need this to avoid an O(N^2) search for "unanswered questions"
-- (made worse by stupid Illustra's inability to cache after a sequential
-- scan)

-- don't think we need this anymore because we never ask for
-- refers_to without a topic spec (hence the new_questions 
-- concat index will work fine)
-- OOOps *** we do in fact need this for the unanswered questions

create index bboard_index_by_refers_to on bboard ( refers_to );

-- this is designed to make checking for already posted messages faster
-- on a system where not all of the messages are in one TOPIC then
-- this should be a concatenated index on topic, one_line

create index bboard_index_by_one_line on bboard ( one_line );
-- don't need this anymore because "new_questions one works"
-- create index bboard_by_topic on bboard ( topic );

-- let's try to make the very top-level query load faster

create index bboard_for_new_questions on bboard ( topic_id, refers_to, posting_time );

-- let's try to make the "postings in one category" faster

create index bboard_for_one_category on bboard ( topic_id, category, refers_to );

-- you might want this depending on how you think custom sort keys are handled
-- can't have just custom_sort_key unique because then you can't have the 
-- same one for two topics

-- create unique index bboard_index_custom on bboard ( topic_id, custom_sort_key );


-- let's try to make the "first N days" query fast
-- create index bboard_for_top_N on bboard using btree ( topic, refers_to, posting_time );
-- fails: W01P0G:warning: index hint for range variable bboard is unusable

-- takes a sort_key and returns just the six digit root
-- doesn't work as well as you'd think because you can't 
-- GROUP BY a functional result

--create function bboard_root_msg(text) returns char(6)
--as 
--return substring ( $1 from 1 for 6 );

create view bboard_new_answers_helper 
as
select b.root_msg_id, b.topic_id, b.posting_time from bboard b
where b.refers_to is not null;

create function bboard_uninteresting_p (integer)
returns char
AS
'
BEGIN
  IF ($1 < 4) THEN
    return ''t'';  
  ELSE
    return ''f'';
  END IF;
END;
' language 'plpgsql';

--create index bboard_pls_index on bboard using pls
--( one_line, message, email, name );

create table msg_id_generator (
	last_msg_id	char(6)
);

-- Apparently Ben and I (DRB) ran into similar problems trying
-- to port over the generator, and both punted and just did an
-- insert...but this insert's a little fancier than his, because
-- it only inserts if the table's empty.

insert into msg_id_generator 
select '000000'
where 0 = (select count(*) from msg_id_generator);

--
-- an "email me if changed" system
--

create table bboard_email_alerts (
	user_id		integer not null references users,
	topic_id	integer not null references bboard_topics,
	valid_p		boolean default 't',	-- we set this to 'f' if we get bounces
	frequency	varchar(30),		-- 'instant', 'daily', 'Monday/Thursday', 'weekly', etc.	
	keywords	varchar(2000)	-- stuff the user is interested in
);

create index bboard_email_alerts_idx on bboard_email_alerts(user_id);

-- Alert by thread system; obsoletes notify field in bboard table.
create table bboard_thread_email_alerts (
	thread_id	char(6) references bboard, -- references msg_id of thread root
	user_id		integer references users,
	primary key (thread_id, user_id)
);



--
-- this holds the last time we sent out notices and the total
-- number of messages sent (just for fun)
--

-- had to change name of table from 
-- bboard_email_alerts_last_updates

create table bboard_email_alerts_updates (
	weekly	datetime,
	weekly_total	integer,
	daily	datetime,
	daily_total	integer,
	monthu	datetime,
	monthu_total	integer
);

-- need something to initialize this table 

insert into bboard_email_alerts_updates 
(weekly, weekly_total, daily, daily_total, monthu, monthu_total)
values
(current_timestamp,0,current_timestamp,0,current_timestamp,0);

create function bboard_contains (varchar, varchar, varchar, varchar, varchar)
returns integer as '
declare
  email alias for $1;
  user_name alias for $2;
  one_line alias for $3;
  message alias for $4;
  space_sep_list_untrimmed alias for $5;
  space_sep_list        varchar(32000);
  upper_indexed_stuff   varchar(32000);
  -- if you call this var START you get hosed royally
  first_space           integer;
  score                 integer;
BEGIN 
  space_sep_list := upper(ltrim(rtrim(space_sep_list_untrimmed)));
  upper_indexed_stuff := upper(email || user_name || one_line || substr(message,30000));
  score := 0;
  IF space_sep_list is null or upper_indexed_stuff is null THEN
    RETURN score;  
  END IF;
  LOOP
   first_space := position(space_sep_list in '' '');
   IF first_space = 0 THEN
     -- one token or maybe end of list
     IF position(upper_indexed_stuff in space_sep_list) <> 0 THEN
        RETURN score+10;
     END IF;
     RETURN score;
   ELSE
   -- first_space <> 0
     IF position(upper_indexed_stuff in substr(space_sep_list,1,first_space-1)) <> 0 THEN
        score := score + 10;
     END IF;
   END IF;
    space_sep_list := substr(space_sep_list,first_space+1);
  END LOOP;  
END;
' language 'plpgsql';

-- for geospatialized forum

-- There must be one row for every state, though we guess that you
-- don't have to use the same 10 EPA regions that we used for
-- Scorecard

-- if you want to use this, feed your database the epa-regions.dmp 
-- file that is in the /install directory

-- create table bboard_epa_regions (
-- 	state_name		varchar(30),
--  	fips_numeric_code	char(2),
-- 	epa_region		integer,
-- 	usps_abbrev		char(2),
--         -- "Great Lakes Region", "Central Region", etc. 
--         -- Not very normalized, but easy.... -jsc
--         description             varchar(50)
-- );

-- for uploading files with bboard postings

-- these are stored in a configurable directory

-- we add photos, Word and Excel documents, etc.
-- file_type is "photo", "spreadsheet", "plaintext"
-- "pdf", "html", "word", "miscbinary", "audio"

-- we only allow one upload per message

create sequence bboard_upload_id_sequence;

create table bboard_uploaded_files (
	bboard_upload_id	integer primary key,
	msg_id			char(6) not null unique references bboard,
	file_type		varchar(100),	-- e.g., "photo"
	file_extension		varchar(50), 	-- e.g., "jpg"
	-- can be useful when deciding whether to present all of something
	n_bytes			integer,
	-- what this file was called on the client machine
	client_filename		varchar(4000) not null,
	-- generally the filename will be "*msg_id*-*upload_id*.extension"
	-- where the extension was the originally provided (so 
	-- that ns_guesstype will work)
	filename_stub		varchar(200) not null,
	-- fields that only make sense if this is an image
	caption			varchar(4000),
	-- will be null if the photo was small to begin with
	thumbnail_stub		varchar(200),
	original_width		integer,
	original_height		integer
);

-- Part of a series of security fixes
-- (BMA, spec'ed by aD)
create function bboard_user_can_view_topic_p (integer,integer)	
returns char AS '
DECLARE
	v_user_id alias for $1;
	v_topic_id alias for $2;
	v_read_access varchar(16);	     
	v_group_id    integer;
	v_count       integer;
BEGIN
	select read_access, group_id into v_read_access, v_group_id
	from bboard_topics
	where topic_id = v_topic_id;

	IF v_read_access = ''any'' or v_read_access = ''public'' THEN
	RETURN ''t'';
	END IF;

	select count(*) into v_count
	from user_group_map
	where user_id = v_user_id
	and group_id = v_group_id;      

	IF v_count > 0 THEN
	RETURN ''t'';
	END IF;

	RETURN ''f'';
END;
' language 'plpgsql';



-- Extra added stuff for Postgres
-- by Ben
create function bboard_mod_pol_number(varchar) returns integer
as '
declare
	mod_pol alias for $1;
begin
	if mod_pol=''''
	then return 1;
	end if;
	
	if mod_pol= ''featured''
	then return 2;
	end if;

	if mod_pol= ''moderated''
	then return 3;
	end if;

	if mod_pol= ''unmoderated''
	then return 4;
	end if;

	if mod_pol= ''private''
	then return 5;
	end if;

	return 6;
end;
' language 'plpgsql';
