--
-- /www/doc/sql/education.sql
--
-- by randyg@mit.edu and aileen@mit.edu on September 29, 1999
-- with much help from philg@arsdigita.com on October 28, 1999

-- the system is centered around the classes
-- as it is too difficult to have 2 types of queries to support 
-- individual users

-- instead of having a classes table, we just define a user group
-- type of "edu_class" 

-- for the class name we'll use "group_name" (a default field from
-- the user_groups table); everything else will have to be in
-- edu_class_info; this is a bit tricky since we need to
-- keep the definitions for the helper edu_classes_info table in
-- sync with what we insert into user_group_type_fields (used
-- to generate UI)

-- we don't store much contact info in the classes table; if we need
-- to send out a report on system usage, we send it to all the people
-- with the admin role in this user group


-- this table holds the terms for the classes (e.g. Fall 1999)
 
create sequence edu_term_id_sequence start 1;

create table edu_terms (
     term_id         integer not null primary key,
     term_name       varchar(100) not null,
     start_date      datetime not null,
     end_date        datetime not null
);

-- we want the above table to automatically start with a term that extends over all time
-- (or at least 100 years) for classes that people take at their own pace

insert into edu_terms (term_id, term_name, start_date, end_date) 
select nextval('edu_term_id_sequence'), 'No Term', sysdate(), sysdate()+'1200 months'::reltime
from dual
where 0 = (select count(*) from edu_terms);

-- for a multi-department university, we need to this to sort courses
-- by department; we're going to want private discussion groups, etc. 
-- for people who work in departments, so we make this a user group

-- to find the department head and other big staffers, we look at people with
-- particular roles in the user_group_map

create table edu_department_info (
	group_id 		integer primary key references user_groups,
	-- for schools like MIT where each department has a number
	department_number	varchar(100),
	-- we'll generate a home page for them but if they have one already
	-- we can provide a link
	external_homepage_url	varchar(200),
	mailing_address		varchar(200),
	phone_number		varchar(20),
	fax_number		varchar(20),
	inquiry_email		varchar(50),
	description		varchar(4000),
	mission_statement	varchar(4000),
	last_modified          	datetime default current_timestamp not null,
        last_modifying_user    	integer references users,
        modified_ip_address    	varchar(20)
);


-- we want to audit the department information

create table edu_department_info_audit (
	group_id 		integer,
	department_number	varchar(100),
	external_homepage_url	varchar(200),
	mailing_address		varchar(200),
	phone_number		varchar(20),
	fax_number		varchar(20),
	inquiry_email		varchar(50),
	description		varchar(4000),
	mission_statement	varchar(4000),
	last_modified          	datetime,
        last_modifying_user    	integer,
        modified_ip_address    	varchar(20)
);


-- we create a trigger to keep the audit table current

create function trig_edu_department_audit_tr()
returns opaque as '
DECLARE
BEGIN
   insert into edu_department_info_audit (
	group_id,
	department_number,
	external_homepage_url,
	mailing_address,
	phone_number,
	fax_number,
	inquiry_email,
	description,
	mission_statement,
	last_modified,
        last_modifying_user,
        modified_ip_address)
     values (
	OLD.group_id,
	OLD.department_number,
	OLD.external_homepage_url,
	OLD.mailing_address,
	OLD.phone_number,
	OLD.fax_number,
	OLD.inquiry_email,
	OLD.description,
	OLD.mission_statement,
	OLD.last_modified,
        OLD.last_modifying_user,
        OLD.modified_ip_address);

    return NEW;
end;
' language 'plpgsql';

create trigger edu_department_info_audit
before update or delete on edu_department_info
for each row
execute procedure trig_edu_department_audit_tr();

-- now, lets create a group of type department and insert all of
-- the necessary rows to generate the user interface on the /admin pages

create function init_education() returns integer as '
declare	
 n_departments_group_types integer;
begin
 select count(*) into n_departments_group_types from user_group_types where group_type = ''edu_department'';
  if n_departments_group_types = 0 then
    insert into user_group_types
    (group_type, pretty_name, pretty_plural, approval_policy, default_new_member_policy, group_module_administration)
    values
   (''edu_department'',''Department'',''Departments'',''wait'',''open'',''full'');

   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_department'', ''department_number'', ''Department Number'', ''text'', ''varchar(100)'', NULL, 1);

   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_department'', ''external_homepage_url'', ''External Homepage URL'', ''text'', ''varchar(200)'', NULL, 2);

   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_department'', ''mailing_address'', ''Mailing Address'', ''text'', ''varchar(200)'', NULL, 3);

   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_department'', ''phone_number'', ''Phone Number'', ''text'', ''varchar(20)'', NULL, 4);

   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_department'', ''fax_number'', ''Fax Number'', ''text'', ''varchar(20)'', NULL, 5);

   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_department'', ''inquiry_email'', ''Inquiry Email'', ''text'', ''varchar(50)'', NULL, 6);

   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_department'', ''description'', ''Description'', ''text'', ''varchar(4000)'', NULL, 7);

   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_department'', ''mission_statement'', ''Mission Statement'', ''text'', ''varchar(4000)'', NULL, 8);

  end if;
  return 1;
end;
' language 'plpgsql';

select init_education();


-- now we want to create a view to easily select departments

create view edu_departments
as
select
	user_groups.group_id as department_id,
	group_name as department_name,
	department_number,
	external_homepage_url,
	mailing_address,
	phone_number,
	fax_number,
	inquiry_email,
	description,
	mission_statement
from user_groups, edu_department_info
where user_groups.group_id = edu_department_info.group_id
and group_type = 'edu_department'
and active_p = 't'
and approved_p = 't';



-- we model the subjects offered by departments

create sequence edu_subject_id_sequence;

-- we don't store the subject number in edu_subjects because a joint subject
-- may have more than one number 

create table edu_subjects (
	subject_id		integer primary key,
	subject_name		varchar(100) not null,
	description		varchar(4000),
	-- at MIT this will be a string like "3-0-9"
	credit_hours		varchar(50),	
	prerequisites		varchar(4000),
	professors_in_charge	varchar(200),
	last_modified          	datetime default current_timestamp not null,
        last_modifying_user    	integer not null references users,
        modified_ip_address    	varchar(20) not null
);


-- we want to audit edu_subjects

create table edu_subjects_audit (
	subject_id		integer,
	subject_name		varchar(100),
	description		varchar(4000),
	credit_hours		varchar(50),	
	prerequisites		varchar(4000),
	professors_in_charge	varchar(200),
	last_modified          	datetime,
        last_modifying_user    	integer,
        modified_ip_address    	varchar(20)
);


-- we create a trigger to keep the audit table current

create function trig_edu_subjects_audit_trigger()
returns opaque as '
DECLARE
begin
   insert into edu_subjects_audit (
	subject_id,
	subject_name,
	description,
	credit_hours,
	prerequisites,
	professors_in_charge,
	last_modified,
        last_modifying_user,
        modified_ip_address)
   values (
	OLD.subject_id,
	OLD.subject_name,
	OLD.description,
	OLD.credit_hours,
	OLD.prerequisites,
	OLD.professors_in_charge,
	OLD.last_modified,
        OLD.last_modifying_user,
        OLD.modified_ip_address);
   return NEW;
end;
' language 'plpgsql';

create trigger edu_subjects_audit_trigger
before update or delete on edu_subjects
for each row
execute procedure trig_edu_subjects_audit_trigger();


create table edu_subject_department_map (
	department_id		integer references user_groups,
	subject_id		integer references edu_subjects,
	-- this would be the full '6.014' or 'CS 101'
	subject_number		varchar(20),
        grad_p                  char(1) default 'f' check(grad_p in ('t','f')),
	primary key ( department_id, subject_id )
);


-- now we create classes.  A class is a particular subject being taught in a particular
-- term.  However, we can also have special cases where a class is not associated with 
-- a term and we can even have classes that stand by themselves and aren't associated with
-- subjects, e.g., an IAP knitting course  (IAP = MIT's Independent Activities Period)

-- the PL/SQL statement cannot create the table so we do it here.
-- create a table to hold the extra info for each group of type
-- 'edu_classes'

create table edu_class_info (
	group_id		integer not null primary key references user_groups,
	term_id			integer references edu_terms,
	subject_id		integer references edu_subjects,
	-- if the class doesn't start or end on the usual term boundary, fill these in
	start_date		datetime,
	end_date		datetime,
	description 		varchar(4000),
	-- at MIT, something like 'Room 4-231, TR 1-2:30'
	where_and_when		varchar(4000),
	-- I still don't agree with this column.  I think we should use
	-- the file system to hold this and just keep a pointer to the 
	-- syllabus.  That way we would have versioning which we do not 
	-- have now (randyg@arsdigita.com, November, 1999)
	syllabus_id		integer references fs_files,
	-- we keep references to the class folders so that we can link to them directly
	-- from various different parts of the system.
	assignments_folder_id		integer references fs_files,
	projects_folder_id		integer references fs_files,
	lecture_notes_folder_id		integer references fs_files,
	handouts_folder_id		integer references fs_files,
	exams_folder_id			integer references fs_files,
	-- will the class web page and the documents on it be open to the public?
	public_p			char(1) default 'f' check(public_p in ('t','f')),
	-- do students receive grades?
	grades_p  		        char(1) default 'f' check(grades_p in ('t','f')),
	-- will the class be divided into teams?
	teams_p			 	char(1) default 'f' check(teams_p in ('t','f')),
	exams_p                  	char(1) default 'f' check (exams_p in ('t', 'f')),
	-- does the class have a final exam?
	final_exam_p                	char(1) default 'f' check (final_exam_p in ('t','f')),
	last_modified           	datetime default current_timestamp not null,
        last_modifying_user     	integer references users,
        modified_ip_address     	varchar(20)
);

-- this table audits edu_class_info
create table edu_class_info_audit (
	group_id		integer,
	term_id			integer,
	subject_id		integer,
	start_date		datetime,
	end_date		datetime,
	description 		varchar(4000),
	where_and_when		varchar(4000),
	syllabus_id		integer,
	assignments_folder_id	integer,
	projects_folder_id	integer,
	lecture_notes_folder_id integer,
	handouts_folder_id	integer,
	exams_folder_id		integer,
	public_p		char(1),
	grades_p  		char(1),
	teams_p			char(1),
	exams_p                 char(1),
	final_exam_p            char(1),
	last_modified          	datetime,
        last_modifying_user     integer,
        modified_ip_address     varchar(20)
);



-- we create a trigger to keep the audit table current
-- drop function trig_edu_class_info_audit_trigger();
create function trig_edu_class_info_audit_trigger()
returns opaque as '
DECLARE
BEGIN
   insert into edu_class_info_audit (
	group_id,		
	term_id,			
	subject_id,		
	start_date,		
	end_date,		
	description, 		
	where_and_when,		
	syllabus_id,		
	assignments_folder_id,	
	projects_folder_id,	
	lecture_notes_folder_id, 
	handouts_folder_id,	
	exams_folder_id,		
	public_p,		
	grades_p,  		
	teams_p,			
	exams_p,                 
	final_exam_p,            
	last_modified,          	
        last_modifying_user,     
        modified_ip_address)
    values (
	OLD.group_id,		
	OLD.term_id,			
	OLD.subject_id,		
	OLD.start_date,		
	OLD.end_date,		
	OLD.description, 		
	OLD.where_and_when,		
	OLD.syllabus_id,		
	OLD.assignments_folder_id,	
	OLD.projects_folder_id,	
	OLD.lecture_notes_folder_id, 
	OLD.handouts_folder_id,	
	OLD.exams_folder_id,		
	OLD.public_p,		
	OLD.grades_p,  		
	OLD.teams_p,			
	OLD.exams_p,                 
	OLD.final_exam_p,            
	OLD.last_modified,          	
        OLD.last_modifying_user,     
        OLD.modified_ip_address);
    return NEW;
END;
' language 'plpgsql';

-- drop trigger edu_class_info_audit_trigger on edu_class_info;
create trigger edu_class_info_audit_trigger
before update or delete on edu_class_info
for each row
execute procedure trig_edu_class_info_audit_trigger();


create function init_education_2() returns integer as '
declare
 n_classes_group_types integer;
begin
 select count(*) into n_classes_group_types from user_group_types where group_type = ''edu_class'';
  if n_classes_group_types = 0 then
	insert into user_group_types
  (group_type, pretty_name, pretty_plural, approval_policy, default_new_member_policy, group_module_administration)
  values
  (''edu_class'',''Class'',''Classes'',''wait'',''open'',''full'');


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''term_id'', ''Term Class is Taught'', ''text'', ''integer'', ''not null references edu_terms'', 1);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''subject_id'', ''Subject'', ''text'', ''integer'', ''not null references edu_subjects'', 2);


   insert into user_group_type_fields 
   (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
  (''edu_class'', ''start_date'', ''Date to Start Displaying Class Web Page'', ''datetime'', ''datetime'', NULL, 3);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''end_date'', ''Date to Stop Displaying Class Web Page'', ''datetime'', ''datetime'', NULL, 4);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''description'', ''Class Description'', ''text'', ''varchar(4000)'', NULL, 5);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''where_and_when'', ''Where and When'', ''text'', ''varchar(4000)'', NULL, 6);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''syllabus_id'', ''Syllabus ID'', ''integer'', ''integer'', ''references fs_files'', 7);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''assignments_folder_id'', ''Assignments Folder'', ''integer'', ''integer'', ''references fs_files'', 8);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''projects_folder_id'', ''Projects Folder'', ''integer'', ''integer'', ''references fs_files'', 8.5);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''lecture_notes_folder_id'', ''Lecture Notes Folder'', ''integer'', ''integer'', ''references fs_files'', 9);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''handouts_folder_id'', ''Handouts Folder'', ''integer'', ''integer'', ''references fs_files'', 10);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''public_p'', ''Will the web page be open to the public?'', ''boolean'', ''char(1)'', ''default ''''t'''' check(public_p in (''''t'''',''''f''''))'', 11);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''grades_p'', ''Do students recieve grades?'', ''boolean'', ''char(1)'',''default ''''f'''' check(grades_p in (''''t'''',''''f''''))'', 12);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''teams_p'', ''Will the class be divided into teams?'', ''boolean'', ''char(1)'',''default ''''f'''' check(teams_p in (''''t'''',''''f''''))'', 13);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''exams_p'', ''Will the class have exams?'', ''boolean'', ''char(1)'',''default ''''f'''' check(exams_p in (''''t'''',''''f''''))'', 14);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''final_exam_p'', ''Will the class have a final exam?'', ''boolean'', ''char(1)'',''default ''''f'''' check(final_exam_p in (''''t'''',''''f''''))'', 15);

   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_class'', ''exams_folder_id'', ''Exams Folder'', ''integer'', ''integer'', ''references fs_files'', 16);

  end if;
  return 0;
end;
' language 'plpgsql';

select init_education_2();

-- create a view for current classes whose webpages we should display 
-- to students

create view edu_current_classes
as
select
	user_groups.group_id as class_id,
	group_name as class_name,
	edu_class_info.term_id,
	subject_id,
	edu_class_info.start_date,
	edu_class_info.end_date,
	description,
	where_and_when,
	syllabus_id,
	lecture_notes_folder_id,
	handouts_folder_id,
	assignments_folder_id,
	projects_folder_id,
	exams_folder_id,
	public_p,
	grades_p,
	teams_p,
	exams_p,
	final_exam_p
from user_groups, edu_class_info
where user_groups.group_id = edu_class_info.group_id
and group_type = 'edu_class'
and active_p = 't'
and existence_public_p='t'
and approved_p = 't'
and sysdate() < edu_class_info.end_date
and sysdate() >= edu_class_info.start_date;

-- create a view for all active classes in the system - these are so
-- professors can access the admin pages even though students don't see
-- these classes

create view edu_classes
as
select
	user_groups.group_id as class_id,
	group_name as class_name,
	edu_class_info.term_id,
	subject_id,
	edu_class_info.start_date,
	edu_class_info.end_date,
	description,
	where_and_when,
	syllabus_id,
	lecture_notes_folder_id,
	handouts_folder_id,
	assignments_folder_id,
	projects_folder_id,
	exams_folder_id,
	public_p,
	grades_p,
	teams_p,
	exams_p,
	final_exam_p
from user_groups, edu_class_info
where user_groups.group_id = edu_class_info.group_id
and group_type = 'edu_class'
and active_p = 't'
and existence_public_p='t'
and approved_p = 't';



-- now, we want to be able to store information about each individual in
-- a class so we create an entry in user_group_type_member_fields

insert into user_group_type_member_fields 
(group_type, role, field_name, field_type, sort_key) 
values 
('edu_class', 'student', 'Institution ID', 'short_text', 1);

insert into user_group_type_member_fields 
(group_type, role, field_name, field_type, sort_key) 
values 
('edu_class', 'dropped', 'Institution ID', 'short_text', 2);

insert into user_group_type_member_fields 
(group_type, role, field_name, field_type, sort_key) 
values 
('edu_class', 'student', 'Student Account', 'short_text', 3);

insert into user_group_type_member_fields 
(group_type, role, field_name, field_type, sort_key) 
values 
('edu_class', 'dropped', 'Student Account', 'short_text', 4);

insert into user_group_type_member_fields 
(group_type, role, field_name, field_type, sort_key) 
values
('edu_class', 'ta', 'Office', 'short_text', 5);

insert into user_group_type_member_fields 
(group_type, role, field_name, field_type, sort_key) 
values
('edu_class', 'professor', 'Office', 'short_text', 6);

insert into user_group_type_member_fields 
(group_type, role, field_name, field_type, sort_key) 
values
('edu_class', 'professor', 'Phone Number', 'short_text', 7);

insert into user_group_type_member_fields 
(group_type, role, field_name, field_type, sort_key) 
values
('edu_class', 'ta', 'Phone Number', 'short_text', 8);

insert into user_group_type_member_fields 
(group_type, role, field_name, field_type, sort_key) 
values
('edu_class', 'ta', 'Office Hours', 'short_text', 9);

insert into user_group_type_member_fields 
(group_type, role, field_name, field_type, sort_key) 
values
('edu_class', 'professor', 'Office Hours', 'short_text', 10);




-- we want to be able to divide classes further into sections.
-- this is nice for tutorials and recitations.  

-- you can get the class for the section from the parent_group_id from user_groups

create table edu_section_info (
	group_id		integer not null references user_groups,
	section_time		varchar(100),
	section_place		varchar(100)
);



create function init_education_3() returns integer as '
declare	
 n_section_group_types integer;
begin
 select count(*) into n_section_group_types from user_group_types where group_type = ''edu_section'';
  if n_section_group_types = 0 then
    insert into user_group_types
    (group_type, pretty_name, pretty_plural, approval_policy, default_new_member_policy,group_module_administration)
    values
   (''edu_section'',''Section'',''Sections'',''wait'',''open'',''full'');


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_section'', ''section_time'', ''Section Time'', ''text'', ''varchar(100)'', NULL, 2);


   insert into user_group_type_fields (group_type, column_name, pretty_name, column_type, column_actual_type, column_extra, sort_key) 
   values
   (''edu_section'', ''section_place'', ''Section Place'', ''text'', ''varchar(100)'', NULL, 3);

  end if;
  return 0;
end;
' language 'plpgsql';

select init_education_3();


create view edu_sections
as
select
	user_groups.group_id as section_id,
	group_name as section_name,
	parent_group_id as class_id,
	section_time,
 	section_place
from user_groups, edu_section_info
where user_groups.group_id = edu_section_info.group_id
and group_type = 'edu_section'
and active_p = 't'
and approved_p = 't';



create function init_education_4()
returns integer as '
declare	
 n_classes_group_types integer;
begin
 select count(*) into n_classes_group_types from user_group_types where group_type = ''edu_department'';
  if n_classes_group_types = 0 then
    insert into user_group_types
    (group_type, pretty_name, pretty_plural, approval_policy, default_new_member_policy, group_module_administration)
    values
   (''edu_department'',''Department'',''Departments'',''wait'',''open'',''none'');
  end if;
  return 0;
end;
' language 'plpgsql';

select init_education_4();

-- we are implementing teams as subgroups so lets create a view to see them


create view edu_teams
as
select
	group_id as team_id,
	group_name as team_name,
	parent_group_id as class_id,
	admin_email,
	registration_date,
	creation_user,
	creation_ip_address,
	existence_public_p,
	new_member_policy,
	email_alert_p,
	multi_role_p,
	group_admin_permissions_p,
	index_page_enabled_p,
	body,
	html_p,
	modification_date,
	modifying_user
from user_groups
where group_type = 'edu_team'
and active_p = 't'
and approved_p = 't';

-- Create edu_team group type
create function init_education_5() returns integer as '
declare	
 n_teams_group_types integer;
begin
 select count(*) into n_teams_group_types from user_group_types where group_type = ''edu_team'';
  if n_teams_group_types = 0 then
    insert into user_group_types
    (group_type, pretty_name, pretty_plural, approval_policy, default_new_member_policy,group_module_administration)
    values
   (''edu_team'',''Team'',''Teams'',''wait'',''open'',''none'');
  end if;
  return 0;
end;
' language 'plpgsql';

select init_education_5();

create sequence edu_textbooks_sequence start 1;

create table edu_textbooks (
	textbook_id	integer not null primary key,
	title		varchar(200),
	author		varchar(400),
	publisher	varchar(200),
        -- isbn has to be a varchar and not a number because some ISBNs have the letter
        -- x at the end; ISBN will be just the digits and letters mushed together
	-- (no dashes in between), amazon.com style
        isbn               varchar(50)
);


-- map the textbooks to classes

create table edu_classes_to_textbooks_map (
	textbook_id	integer references edu_textbooks,
	class_id	integer references user_groups,
	required_p	char(1) default 't' check (required_p in ('t','f')),
	comments		varchar(4000),
	primary key (class_id, textbook_id)
);




create sequence edu_grade_sequence;

-- records the grade types and their relative weights. this table will not 
-- capture the qualitative factors, but should take care of the
-- quantitative portion of the final grade
create table edu_grades (
       grade_id		       integer not null primary key,
       grade_name	       varchar(100),
       class_id		       integer not null references user_groups,
       comments		       varchar(4000),
       -- weight is a percentage
       weight		       numeric check (weight between 0 and 100),
       last_modified           datetime default current_timestamp not null,
       last_modifying_user     integer not null references users,
       modified_ip_address     varchar(20) not null
);

-- we want to audit edu_grades

create table edu_grades_audit (
       grade_id		       integer,
       grade_name	       varchar(100),
       class_id		       integer,
       comments		       varchar(4000),
       -- weight is a percentage
       weight		       numeric,
       last_modified           datetime,
       last_modifying_user     integer,
       modified_ip_address     varchar(20),
       delete_p                char(1) default('f') check (delete_p in ('t','f'))
);


-- we create a trigger to keep the audit table current
create function trig_edu_grades_audit_trigger()
returns opaque as '
DECLARE
begin
   insert into edu_grades_audit (
        grade_id,
        grade_name,
	class_id,
	comments,
	weight,
	last_modified,
	last_modifying_user,
	modified_ip_address)
    values (
        OLD.grade_id,
        OLD.grade_name,
	OLD.class_id,
	OLD.comments,
	OLD.weight,
	OLD.last_modified,
	OLD.last_modifying_user,
	OLD.modified_ip_address);
    return NEW;
end;
' language 'plpgsql';

create trigger edu_grades_audit_trigger
before update or delete on edu_grades
for each row
execute procedure trig_edu_grades_audit_trigger();


-- we want to be able to easily keep track of lecture notes/handouts
-- note that we do not keep track of author or date uploaded or even
-- a comment about it.  We do not because is all kept in the
-- fs_files table, which edu_handouts references.  We keep the handout_name
-- in both places because we will be displaying that a lot and we do not
-- want to always have to join with fs_files

create sequence edu_handout_id_sequence start 1;

create table edu_handouts (
	handout_id		integer not null primary key,
	class_id		integer references user_groups,
	handout_name		varchar(500) not null,
	file_id			integer references fs_files not null,
	-- what kind of handout is this?  Possibilities include
	-- lecture_notes and announcement
	handout_type		varchar(200),
	-- what date was this handout given out
	distribution_date 	datetime default current_timestamp
);




-- we want to be able to keep track of assignemnts within the class.

create sequence edu_task_sequence;

-- includes assignments, projects, exams, any tasks a student might be
-- graded on

create table edu_student_tasks (
	task_id		integer primary key,
	class_id 	integer not null references user_groups,
	grade_id	integer references edu_grades,
        -- we have to have a task type so we can categorize tasks in the
        -- user pages
	task_type	varchar(100) check (task_type in ('assignment', 'exam', 'project')),
	task_name	varchar(100),
	description	varchar(4000),
	-- the date we assigned/created the task
	date_assigned	datetime,
	-- we want to know the last time the task was modified
	-- (the permissions were changed or a new version was uploaded, etc)
        last_modified   datetime,
	-- could be date assignment is due, or date of an exam
	due_date	datetime,
	-- this references the fs_files that holds either the 
	-- actual assignment available for download or the url of the
	-- assignment
	file_id		integer references fs_files,
	-- who assigned this?
	assigned_by	integer not null references users,
        -- This column is for projects where students can
        -- assign themselves to teams.
        self_assignable_p char(1) default 'f' check (self_assignable_p in ('t','f')),
        self_assign_deadline    datetime,
	-- how much is this assignment worth compared to the others with
        -- the same grade_id (e.g. under the same grade group)?
	-- weight is a percentage	
	weight			numeric check (weight between 0 and 100),
	requires_grade_p char(1) check (requires_grade_p in ('t','f')),
	-- whether the task is submitted/administered online
	online_p char(1) check (online_p in ('t','f')),
	-- if an assignment has been deleted we mark it as inactive
	active_p char(1) default 't' check (active_p in ('t','f'))
);


-- views for assignments, exams, and projects
create view edu_projects
as 
  select
  task_id as project_id,
  class_id,
  task_type,
  assigned_by as teacher_id,
  grade_id,
  task_name as project_name,
  description,
  date_assigned,
  last_modified,
  due_date,
  file_id,
  weight,
  requires_grade_p,
  online_p as electronic_submission_p
from edu_student_tasks 
where task_type='project'
and active_p='t';

create view edu_exams
as
  select
  task_id as exam_id,
  task_type,
  class_id,
  assigned_by as teacher_id,
  grade_id,
  task_name as exam_name,
  description as comments,
  date_assigned as creation_date,
  last_modified,
  due_date as date_administered,
  file_id,
  weight,
  requires_grade_p,
  online_p
from edu_student_tasks 
where task_type='exam'
and active_p='t';

create view edu_assignments 
as 
  select 
  task_id as assignment_id,
  task_type,
  class_id,
  assigned_by as teacher_id,
  grade_id,
  task_name as assignment_name,
  description,
  date_assigned,
  last_modified,
  due_date,
  file_id,
  weight,
  requires_grade_p,
  online_p as electronic_submission_p
from edu_student_tasks
where task_type = 'assignment'
and active_p='t';


-- we want to be able to post the solutions and associate the solutions
-- to a given file

create table edu_task_solutions (
	task_id			integer references edu_student_tasks,
	file_id			integer references fs_files,
	primary key(task_id, file_id)
);



-- we want a table to map student solutions to assignments
-- this is what allows students to upload their finished papers, etc.

create table edu_student_answers (
	student_id		integer references users,
	task_id			integer references edu_student_tasks,
	file_id			integer references fs_files,
	-- this is the date of the last time the solutions were changed
	last_modified           datetime default current_timestamp not null,
        last_modifying_user     integer not null references users,
	-- modified_ip_address is stored as a string separated by periods.
        modified_ip_address     varchar(20) not null
);


create table edu_student_answers_audit (
	student_id		integer,
	task_id			integer,
	file_id			integer,
	-- this is the date of the last time the solutions were changed
	last_modified           datetime,
        last_modifying_user     integer,
	-- modified_ip_address is stored as a string separated by periods.
        modified_ip_address     varchar(20)
);


-- we create a trigger to keep the audit table current

create function trig_edu_student_answers_audit()
returns opaque as '
DECLARE
begin
	insert into edu_student_answers_audit (
	    	student_id,
		task_id,
		file_id,
		last_modified,
		last_modifying_user,
		modified_ip_address)
           values (
	    	OLD.student_id,
		OLD.task_id,
		OLD.file_id,
		OLD.last_modified,
		OLD.last_modifying_user,
		OLD.modified_ip_address);  
         return NEW;                   
end;
' language 'plpgsql';

create trigger edu_student_answers_audit
before update or delete on edu_student_answers
for each row
execute procedure trig_edu_student_answers_audit();



-- this is where we keep the student grades and the evaluations
-- that students receive from teachers

create sequence edu_evaluation_id_sequence;

create table edu_student_evaluations (
	evaluation_id		integer primary key,
	class_id		integer not null references user_groups,
        -- must have student_id or team_id 
	student_id		integer references users,
	team_id			integer references user_groups,
	task_id		        integer references edu_student_tasks,
	-- there may be several times during the term that the prof 
        -- wants to evaluate a student.  So, the evaluation_type 
        -- is something like 'end_of_term' or 'midterm'
	evaluation_type		varchar(100),
	grader_id		integer not null references users,
	grade			varchar(5),
	comments		varchar(4000),
	show_student_p		char(1) default 't' check (show_student_p in ('t','f')),
	evaluation_date		datetime default current_timestamp,
	last_modified           datetime default current_timestamp not null,
        last_modifying_user     integer not null references users,
	-- modified_ip_address is stored as a string separated by periods.
        modified_ip_address     varchar(20) not null
);


-- we want to audit the evaluations table

create table edu_student_evaluations_audit (
	evaluation_id		integer,
	class_id		integer,
        -- must have student_id or team_id 
	student_id		integer,
	team_id			integer,
	task_id		        integer,
	evaluation_type		varchar(100),
	grader_id		integer,
	grade			varchar(5),
	comments		varchar(4000),
	show_student_p		char(1),
	evaluation_date		datetime,
	last_modified           datetime,
        last_modifying_user	integer,
        modified_ip_address     varchar(20)
);


-- now, we want to hold information about each project.  It is possible
-- to have one term project but many instances of that project.  For
-- instance, "Final Project for 6.916" is a term project that would
-- be kept in the edu_tasks table but ArfDigita.org is a project
-- instance that would be kept in this table.  There is a many to 
-- one mapping

-- we make task_id not null because every project has to be part of
-- some sort of task (either an assignment or a project)
-- we make it a task because all evaluations are done on tasks

create sequence edu_project_instance_id_seq start 1;

create table edu_project_instances  (
	project_instance_id 	integer not null primary key,
	project_instance_name	varchar(200),
	project_instance_url	varchar(500),
	-- which project is this an instance of?
	project_id		integer not null references edu_student_tasks,
	description		varchar(4000),
	approved_p		char(1) default 'f' check(approved_p in ('t','f')),
        approved_date           datetime,
        approving_user          integer references users(user_id),
	-- we want to be able to generate a consistent user interface so
	-- we record the type of project.  
	project_type		varchar(10) default 'team' check(project_type in ('user','team')),
	min_body_count		integer,
	max_body_count		integer,
	-- we want to be able to "delete" project instances so we have active_p
	active_p		char(1) default 't' check(active_p in ('t','f'))
);




-- we want to be able to assign students and teams to projects

create table edu_project_user_map (
	project_instance_id	integer not null references edu_project_instances,
	team_id			integer references user_groups,
	student_id		integer references users,
	constraint edu_project_user_map_check check ((team_id is null and student_id is not null) or (team_id is not null and student_id is null))
);

create index edu_project_map_idx on edu_project_user_map(project_instance_id, team_id, student_id);


-- we want to allow classes to rename their roles.  That is,
-- some people want to be called Professor where others want
-- to be called Instructor and still others want to be called
-- Lecturer.  We don't want to just use the 'role' column
-- in user_group_roles because then we would not have a way
-- to "spam all professors and TAs" because we would not know
-- which role was a prof and which was a TA.  Also, we want to
-- have a sort_key so that we know which order to display these
-- items when they are shown to the user.  So, we have the following
-- table

-- so, for the case where a class wants to call the prof a Lecturer, 
-- we would have role = Professor and pretty_role = Lecturer

create table edu_role_pretty_role_map (
	group_id		integer not null references user_groups,
	-- role should reference user_group_roles(role)
	role			varchar(200),
	-- what the class wants to call the role
	pretty_role		varchar(200),
	pretty_role_plural 	varchar(300),
	-- sort key for display of columns.
	sort_key		integer not null,
	-- this is to capture info about the hierarchy of role permissions
	priority		integer,
	primary key (group_id, role)
);







-------------------------------------------------
-------------------------------------------------
--
--        	BEGIN PL/SQL 
--
--
-------------------------------------------------
-------------------------------------------------




-- now, we need a trigger to update the table we just created
-- this is included in case people want to add new roles to
-- the class all they have to do insert into user_group_roles
-- and this will take care of the rest

-- PG rocks. No need for this trio stuff (BMA).
create function trig_edu_class_role_update_tr()
returns opaque as '
DECLARE
BEGIN
	v_group_id	user_group_roles.group_id%TYPE;
	v_new_role	user_group_roles.role%TYPE;
	v_class_p	integer;
BEGIN
	select count(user_group_roles.group_id) into v_class_p,
        user_group_roles.group_id into v_group_id, 
        role into v_new_role
	from user_groups, user_group_roles
	where group_type = ''edu_class''
        and user_group_roles.oid = NEW.oid
        and user_group_roles.group_id = user_groups.group_id
        group by user_group_roles.group_id, role;

 	-- if this is a group of type edu_class
	IF v_class_p > 0 THEN
	   -- we want to update the existing row
	   update edu_role_pretty_role_map
           set role = v_new_role
           where group_id = v_group_id
           and role = OLD.role;
	END IF;
	
	return new;
END;
' language 'plpgsql';

CREATE TRIGGER edu_class_role_update_tr
BEFORE UPDATE ON user_group_roles
FOR EACH ROW
execute procedure trig_edu_class_role_update_tr();

-- for every row that is inserted into the user_group_roles, if
-- the group is of type edu_class then we want to insert a corresponding
-- role into edu_role_pretty_role_map

create function trig_edu_class_role_insert_tr()
returns opaque as '
DECLARE
	v_class_p	integer;
BEGIN
	select count(group_id) into v_class_p
	from user_groups
	where group_type = ''edu_class''
        and group_id = NEW.group_id;

	IF v_class_p > 0 THEN

		insert into edu_role_pretty_role_map (
        	       group_id, 
	               role, 
        	       pretty_role,
                       pretty_role_plural, 
	               sort_key,
                       priority) 
        	    select
	               NEW.group_id, 
  		       NEW.role, 
		       NEW.role, 
                       NEW.role || ''s'',
    		       coalesce(max(sort_key),0) + 1,
    		       coalesce(max(priority),0) + 1
	             from edu_role_pretty_role_map
        	    where group_id = NEW.group_id;
	END IF;
	return NEW;
END;
' language 'plpgsql';

CREATE TRIGGER edu_class_role_insert_tr
AFTER INSERT ON user_group_roles
FOR EACH ROW
execute procedure trig_edu_class_role_insert_tr();

-- if a role is delete from user_group_roles and the group
-- is of type edu_class then we also want to delete it from
-- edu_role_pretty_role_map

create function trig_edu_class_role_delete_tr()
returns opaque as '
DECLARE
	v_class_p	integer;
BEGIN
	select count(group_id) into v_class_p
	from user_groups
	where group_type = ''edu_class''
        and group_id = OLD.group_id
	and group_type = ''edu_class'';

	IF v_class_p > 0 THEN

		delete from edu_role_pretty_role_map 
		where group_id = OLD.group_id
                and role = OLD.role;

	END IF;
        return OLD;
END;
' language 'plpgsql';

CREATE TRIGGER edu_class_role_delete_tr
BEFORE DELETE ON user_group_roles
FOR EACH ROW
execute procedure trig_edu_class_role_delete_tr();




---------------------------------------------------
--
--
--
--    begin the portal tables
--
--
--
---------------------------------------------------



-- the portal mini-tables

create sequence weather_id_sequence;


create table portal_weather (
       weather_id    integer not null primary key,
       user_id		    integer not null references users,
       city		    varchar(100),
       usps_abbrev	    char(2) references states,
       zip_code		    varchar(10),
       -- the type can be: next day forecast, 5 day forecast, current conditions
       five_day_p		    char(1) default 'f' check (five_day_p in ('t','f')),
       next_day_p		    char(1) default 'f' check (next_day_p in ('t','f')),
       current_p	    char(1) default 'f' check (current_p in ('t','f'))
);

create table portal_stocks (
       user_id		   integer not null references users,
       symbol		   varchar(10) not null,
       default_p	   char(1) default 'f' check(default_p in ('t','f'))
);

--- we're currently using the calendar module and not edu_calendar
--- because the features have not been fully implemented 
-- this is taken from the intranet calendar
create table edu_calendar_categories (
	category	varchar(100) primary key,
	enabled_p	char(1) default 't' check(enabled_p in ('t','f'))
);

create sequence edu_calendar_id_sequence;

-- updates from intranet/doc/sql/calendar.sql:
-- the addition of a viewable column that specifies whether the calendar
-- entry is viewable by the public and if so, whether we should show the
-- title or something in place of the title (e.g. Busy, Free, Tentative --
-- MS Outlook options). also, addition of owner column that identifies who
-- the entry is for: so we can display calendars with respect to individual
-- users or groups of users (like in a team)

create table edu_calendar (
	calendar_id	integer primary key,
	category	integer not null references calendar_categories,
	-- the way we connect calendar entries to users
	owner		integer not null references users,
	title		varchar(100) not null,
	body		varchar(4000) not null,
	-- is the body in HTML or plain text (the default)
	html_p			char(1) default 'f' check(html_p in ('t','f')),
	start_date	datetime not null,  -- first day of the event
	end_date	datetime not null,  -- last day of the event (same as start_date for single-day events)
	expiration_date	datetime not null,  -- day to stop including the event in calendars, typically end_date
	-- viewable as public means the title will be displayed. private
	-- means the entry will be invisible unless viewed by the
	-- owner. busy, free, or tentative will be displayed instead of title
	-- to viewers other than owner
	viewable	varchar(100) default 'public' check(viewable in
	('public', 'busy', 'free', 'tentative', 'private')),
	event_url	varchar(200),  -- URL to the event
	event_email	varchar(100),  -- email address for the event
	-- for events that have a geographical location
	country_code	char(2) references country_codes(iso),
	-- within the US
	usps_abbrev	char(2) references states,
	-- we only want five digits
	zip_code	varchar(10),
	approved_p	char(1) default 'f' check(approved_p in ('t','f')),
	creation_date	datetime not null,
	creation_user		integer not null references users(user_id),
	creation_ip_address	varchar(50) not null
);

-- create default tables for each portal
-- start a personal category so the user can enter personal events of
-- "user" scope
create function trig_portal_page_upon_new_user()
returns opaque as '
DECLARE
  begin
    insert into portal_pages 
    (page_id, user_id, page_number)
    values
    (nextval(''portal_page_id_sequence''), NEW.user_id, 1);
    insert into calendar_categories (category_id, scope, user_id, category,
enabled_p) 
    values 
    (nextval(''calendar_category_id_sequence''), ''user'', NEW.user_id,
''Personal'', ''t'');    
    return NEW;
  end;
' language 'plpgsql';

create trigger portal_page_upon_new_user
after insert on users
for each row
execute procedure trig_portal_page_upon_new_user();

-- the opposite of the above trigger -- for deleting users
create function trig_portal_remove_upon_user_delete()
returns opaque as '
DECLARE
BEGIN
	delete from portal_pages
	where user_id= OLD.user_id;
	return OLD;  
END;
' language 'plpgsql';

create trigger portal_remove_upon_user_delete
before delete on users
for each row
execute procedure trig_portal_remove_upon_user_delete();

create function trig_portal_setup_upon_page_insert()
returns opaque as '
DECLARE
	stock_table_id		portal_tables.table_id%TYPE;
	weather_table_id	portal_tables.table_id%TYPE;
	classes_table_id	portal_tables.table_id%TYPE;
	announcements_table_id	portal_tables.table_id%TYPE;
	calendar_table_id	portal_tables.table_id%TYPE;
BEGIN
	select table_id into stock_table_id from portal_tables where
table_name=''Stock Quotes'';
	select table_id into weather_table_id from portal_tables where
table_name=''Current Weather'';
	select table_id into classes_table_id from portal_tables where
table_name=''Classes'';
	select table_id into announcements_table_id from portal_tables where
table_name=''Announcements'';
	select table_id into calendar_table_id from portal_tables where
table_name=''Calendar'';
     insert into portal_table_page_map
     (page_id, table_id, sort_key, page_side)
     values
     (NEW.page_id, stock_table_id, 1, ''l'');
     insert into portal_table_page_map
     (page_id, table_id, sort_key, page_side)
     values
     (NEW.page_id, weather_table_id, 2, ''l'');
     insert into portal_table_page_map
     (page_id, table_id, sort_key, page_side)
     values
     (NEW.page_id, classes_table_id, 1, ''r'');
     insert into portal_table_page_map
     (page_id, table_id, sort_key, page_side)
     values
     (NEW.page_id, announcements_table_id, 3, ''l'');
     insert into portal_table_page_map
     (page_id, table_id, sort_key, page_side)
     values
     (NEW.page_id, calendar_table_id, 2, ''r'');
     return NEW;
END;
' language 'plpgsql';

create trigger portal_setup_upon_page_insert
after insert on portal_pages
for each row
execute procedure trig_portal_setup_upon_page_insert();

-- the opposite of the trigger above -- upon deleting a page for portal
-- table we also want to delete the entries from portal_table_page_map
create function trig_portal_update_upon_page_delete()
returns opaque as '
DECLARE
BEGIN
	delete from portal_table_page_map where page_id= OLD.page_id;
    return old;
END;
' language 'plpgsql';


-- drop function edu_count_classes(integer);
create function edu_count_classes(integer) returns integer as '
declare
        id      alias for $1;
        cnt     integer;
begin
        select into cnt count(class_id) 
          from edu_classes, edu_terms
         where edu_classes.term_id = edu_classes.term_id;
        return (case when cnt is null then 0 else cnt end);
end;
' language 'plpgsql';

create trigger portal_update_upon_page_delete
before delete on portal_pages
for each row
execute procedure trig_portal_update_upon_page_delete();


create view solution_files as 
select sol.file_id, version_id, file_extension, url, task_id
  from edu_task_solutions sol, fs_versions_latest ver
 where sol.file_id = ver.file_id;


create view task_files as 
select ver.file_id, version_id, file_extension, url, task_id
  from edu_task_solutions sol, fs_versions_latest ver
 where sol.file_id = ver.file_id;


create view student_files as 
select url, author_id as student_id, file_extension, version_id, task_id, task.file_id 
  from fs_versions_latest ver, edu_student_answers task  
 where task.file_id = ver.file_id;

-- drop view student_answer_files;
create view student_answer_files as 
select file_extension, file_title, url, version_id, task_id, student_id
                    from fs_versions_latest ver,
                         edu_student_answers ans,
                         fs_files
                   where ver.file_id = ans.file_id 
                     and fs_files.file_id = ver.file_id;


create view member_field_mapping as 
select fm.field_name,  fm.field_value, tmf.sort_key,  fm.user_id, group_type
  from user_group_type_member_fields tmf, user_group_member_field_map fm
 where tmf.field_name = fm.field_name;

-- check about distinct in this query DanW
create view user_group_member_field_mapping as
select fm.field_name, 
                   fm.field_value,
                   tmf.sort_key,
                   fm.user_id,
                   fm.group_id
              from user_group_type_member_fields tmf,
                   user_group_member_field_map fm,
                   user_group_map map
             where group_type = 'edu_class'
               and map.group_id = fm.group_id
               and (tmf.role is null or lower(tmf.role) = lower(map.role))
               and lower(tmf.field_name) = lower(fm.field_name);



-- drop view student_answers;
create view student_answers as 
select users.first_names,
               users.last_name,
               eval.grade,
               eval.comments,
               eval.evaluation_id,
               eval.show_student_p,
               eval.task_id,
               eval.grader_id,
               eval.student_id
          from edu_student_evaluations eval,
               users
         where users.user_id = eval.grader_id;


create view role_mapping as 
select pretty_role, map.group_id, map.user_id
               from user_group_map map,
                    edu_role_pretty_role_map role_map
              where lower(role_map.role) = lower(map.role)
                and role_map.group_id = map.group_id;
