-- Definitions for the AREA database
-- Author: Bruno Wolff III
-- Last Revision: 2016-09-04

-- Make sure database is set up properly

-- First connect as postgres superuser

\c - postgres

-- Make sure that not everyone can create schemas or temporary tables

revoke all on database :DBNAME from public;
grant connect on database :DBNAME to public;

-- Delete the public schema if it exists

drop schema public cascade;

-- Create the public schema with limited access

begin;
create schema public;
revoke all on schema public from public;
grant usage on schema public to public;
grant all on schema public to bruno;
end;

-- bruno now has enough rights that we can use his account to finish up

set session authorization bruno;

-- We should now have a clean database with appropiate extensions and
-- can set up the AREA specific stuff.

-- Privacy levels
-- This table is used in views to change fields to null if the privacy
-- level of the request is less than the privacy level of the row.

-- priv   Table name
-- pname  Name to be used to reference the privacy level
-- pord   A number used to order the privacy levels
-- ptxt   A text description that can be used in a prompt

-- pname values:
-- any       The data can be used for anything
-- web       The data can be placed on public web pages
-- forward   The data can be used to forward messages from the public
-- request   The data can be given out in response to one off requests
-- member    The data can be given to other AREA members
-- td        The data can be given to tournament directors
-- admin     The data can be always be seen by the AREA administrators

create table priv (
  pname text primary key,
  pord int4 not null unique constraint pord_nonnegative check (pord >= 0),
  ptxt text
);

-- Allow access to it from the web server account

grant select on priv to public;

-- The exact numbers used isn't relevant as only the ordering matters.
-- However leaving room to insert new levels in without changing
-- old ones seems like a good idea.

copy priv from stdin using delimiters ',';
any,0,No restrictions on access
web,100,Allow anonymous access via the web
forward,200,Allow use in blind forwarding
request,300,Allow access via one off requests
member,400,Allow access by people believed to be AREA members
td,500,Allow access by tournament directors
admin,600,Access by AREA administrators is always allowed
\.

-- Valid generation labels other than roman numerals

-- genlab  The last name suffix to be displayed
-- gen     The generation number this label implies

create table genlabs (
  genlab text constraint bad_genlab check
    (genlab ~ '^[^[:cntrl:][:space:]]+( [^[:cntrl:][:space:]]+)*$'),
  gen int4 constraint bad_generation check (gen > 0 and gen < 4000),
  primary key (genlab, gen)
);

-- Allow access to it from the web server account

grant select on genlabs to public;

-- This stuff is pretty constant so load it at database creation

copy genlabs from stdin using delimiters ',';
Sr,1
Jr,2
\.

-- The current name table definition.
-- This is the primary definition for AREA members
-- There will also need to be a history table to track areaid and name changes
-- so that the admins have a way to check on records to resolve problems.
-- Names are limited to US ASCII characters. They can contain letters (a-z)
-- with case being maintained. They may also have space, ', or - between two
-- letters. They will be sorted using the ASCII ordering with uppercase
-- characters treated as the lowercase equivalent.

-- cname      Table name
-- areaid     Current AREA ID of a person
-- displayid  The fancy cased version of area id
-- lname      Current last name of a person
-- fmname     Current first and middle names of a person
-- aname      Current alternate first and middle names of a person
-- gen        Generation number (Sr or I > 1, Jr or II > 2, III > 3, IV > 4, etc)
-- genlab     A label for the genartion if not using a roman numeral.
-- privacy    Privacy level for their name data
-- touched    When the areaid or name (not privacy) information last changed

create table cname (
  areaid text primary key constraint bad_char_in_id check
    (areaid ~ '^[ABCDEFGHIJKLMNOPQRSTUVWXYZ[:digit:]]{1,6}([.][[:digit:]]{2,})?$')
    constraint missing_lead_zeros check (areaid !~ '^[[:digit:]]{1,4}([.][[:digit:]]+)?$'),
  displayid text constraint ids_must_match check (upper(displayid) = areaid),
  lname text not null constraint bad_last_name check
    (lname ~ '^[[:alpha:]]+([ ''-][[:alpha:]]+)*$'),
  fmname text constraint bad_first_or_mid_name check
    (fmname ~ '^[[:alpha:]]+([ ''-][[:alpha:]]+)*$'),
  aname text constraint bad_alt_name check
    (aname ~ '^[[:alpha:]]+([ ''-][[:alpha:]]+)*$'),
  gen int4 constraint bad_generation check (gen > 0 and gen < 4000),
  genlab text constraint missing_gen check (genlab is null or gen is not null),
  privacy text not null constraint bad_privacy references priv,
  touched timestamp with time zone default current_timestamp not null,
  foreign key (genlab, gen) references genlabs
);

-- I think orderings by last name will be used enough to create an index.
-- The privacy checking may make it not so useful so its impact should
-- be tested. The default ordering is partially case insensitive, but I
-- want to use lower to force different cased last names to be equal.

create index lname_idx on cname (lower(lname));

-- No public access to cname

-- Web view of the above table
-- areaids are always considered public.
-- The name fields will only be available to the web server for
-- those people that said it was OK. This will include search
-- queries using these fields.
-- priv isn't needed and should be considered admin access only

create view cname_web with (security_barrier) as
  select
    areaid,
    displayid,
    case when a.pord <= b.pord then lname end as lname,
    case when a.pord <= b.pord then fmname end as fmname,
    case when a.pord <= b.pord then aname end as aname,
    case when a.pord <= b.pord then gen end as gen,
    case when a.pord <= b.pord then genlab end as genlab,
    case when a.pord <= b.pord then touched end as touched
  from cname, priv a, priv b
  where
    a.pname = privacy and
    b.pname = 'web';

-- Allow access to it from the web server account

grant select on cname_web to public;

-- Publisher table
-- This is used to provide consistant versions of publisher names and
-- a single place to keep track of a URL for the publisher.

-- publishers  Table name
-- pubid       Internal code for a publisher
-- pubname     The name of the publisher
-- puburl      An optional URL for the publisher

create table publishers (
  pubid text primary key constraint bad_char_in_id check
    (pubid ~ '^[[:upper:][:digit:]]+$'),
  pubname text not null constraint bad_char_in_name check
    (pubname ~ '^[^[:cntrl:][:space:]]+( [^[:cntrl:][:space:]]+)*$'),
  puburl text constraint bad_char_in_url check
    (puburl ~ '^[^[:cntrl:][:space:]]+$')
);

-- Allow access to it from the web server account

grant select on publishers to public;

-- Rating type table
-- This table defines valid rating type codes and provides a text description

-- rtype    Rating type code (e.g. WTA, TEAM, RACE, TWO, etc.)
-- descr    A description of the rating type

create table rtypes (
  rtype text primary key constraint bad_char_in_rtype check
    (rtype ~ '^[[:upper:][:digit:]]+$'),
  descr text not null constraint bad_char_in_descr check
    (descr ~ '^[^[:cntrl:][:space:]]+( [^[:cntrl:][:space:]]+)*$')
);

-- Allow access to it from the web server account

grant select on rtypes to public;

-- Contacts table
-- This table defines valid contact addresses (mostly for reporting results)
-- These are all public AREA email addresses

-- contact   Contact code
-- email     Email address

create table contacts (
  contact text primary key constraint bad_char_in_contact check
    (contact ~ '^[[:upper:][:digit:]]+$'),
  email text not null constraint bad_char_in_email check
    (email ~ '^[-_.[:alnum:]]+@[-_.[:alnum:]]+$')
);

-- Allow access to it from the web server account

grant select on contacts to public;

-- Game definition table
-- This is the primary definition for games.
-- Titles may only contain printable US ASCII characters and
-- imbedded spaces. Sorting will be done using the US ASCII colating
-- sequence with uppercase letters treated as lowercase.

-- games    Table name
-- gameid   Initially will be excel sheet name
-- title    The game title
-- rtype    Rating type (e.g. WTA, RACE, TEAM or TWO)
-- sol      Can the game win?
-- contact  Where to send results (by email)
-- touched  Last time gameid, or title (not ratings) changed

create table games (
  gameid text primary key constraint bad_char_in_id check
    (gameid ~ '^[[:upper:][:digit:]]+$'),
  title text unique not null constraint bad_char_in_title check
    (title ~ '^[^[:cntrl:][:space:]]+( [^[:cntrl:][:space:]]+)*$'),
  rtype text not null constraint bad_rtype references rtypes,
  sol bool not null,
  contact text not null constraint bad_contact references contacts,
  touched timestamp with time zone default current_timestamp not null
);

-- This probably won't help searching, but should help displaying the
-- sorted title index.

create index title_idx on games (lower(title));

-- Allow access to it from the web server account

grant select on games to public;

-- Table of publishers for a game

-- gamepubs   Table name
-- gameid    Game being published
-- pubid     One of the game's publishers

create table gamepubs (
  gameid text constraint bad_gameid references games,
  pubid text constraint bad_pubid references publishers,
  primary key (gameid, pubid)
);

-- Allow access to it from the web server account

grant select on gamepubs to public;

-- Table of wbc events (from any year, not just current)

-- wbc     Table name
-- code    A WBC code
-- event   The event title (may not always be known)
-- url     URL for the event page if any

create table wbc (
  code text primary key
    constraint bad_char_in_code check (code ~ '^[^[:cntrl:][:space:]]+$')
    constraint no_slash_in_code check (code !~ '/'),
  event text
    constraint bad_char_in_event check
      (event ~ '^[^[:cntrl:][:space:]]+( [^[:cntrl:][:space:]]+)*$'),
  url text
    constraint bad_char_in_url check (url ~ '^[^[:cntrl:][:space:]]+$')
);

-- For matching searches wbc codes are case insensitive and will be done
-- using lower case.

create unique index wbc_lower on wbc (lower(code));

-- Allow access to it from the web server account

grant select on wbc to public;

-- Table of WBC codes for games
-- There might be muliple entries for a single code or for a single game
-- because WBC events don't map 1 to 1 to games.
-- We can't have slashses (/) in the WBC code since we use the WBC code
-- in generated URLs. Unescaped slashes break relative references and
-- escaped ones are detected as not found before rewriting can catch
-- the request. Setting the base in generated html documents is one
-- possible work around if the WBC uses any in their codes.

-- wbcgames  Table name
-- code      WBC code from wbc table
-- gameid    gameid from games table

create table wbcgames (
  code text constraint bad_wbc_code references wbc,
  gameid text constraint bad_gameid references games,
  primary key (code, gameid)
);

-- Allow access to it from the web server account

grant select on wbcgames to public;

-- Current AREA ratings
-- Note this record should only be created for person - game pairs where
-- either the person has at least one recorded rated game or has specifically
-- notified AREA that they have an interest in the game.
-- This table should be used to retrive data even though it isn't the
-- ultimate source for data. It is too expensive to rederive this information
-- from the base data. If something bad happens, this table should be 
-- rebuildable from a transaction table that includes actions for the
-- results of rated games, expressing interest in a game, or making hand
-- corrections to fix problems.

-- crate    Table name
-- areaid   From the cname table
-- gameid   From the games table
-- rate     Current AREA rating
-- frq      Number of times payed rated games of this game
--          If frq is 0 the player has expressed interest in the game.
-- opp      Total number of different opponents played
-- rmp      Total number of rated games played remotely (PBM, PBEM, VASL, etc.)
-- trn      Total number of rated games played in tournaments played
-- rmc      Total number of rated games played in remote play competitions
-- gm       String indicating when last gm'd
-- touched  Timestamp from when information in this record was changed
--          Typically this should be the last time a match was entered
--          for this person.

create table crate (
  areaid text constraint bad_areaid references cname,
  gameid text constraint bad_gameid references games,
  rate int4 default 5000 not null constraint rate_nonnegative check (rate >= 0)
    constraint rate_other_interested check (frq > 0 or rate = 5000),
  frq int4 default 0 not null constraint frq_nonnegative check (frq >= 0),
  opp int4 default 0 not null constraint opp_nonnegative check (opp >= 0),
  rmp int4 default 0 not null constraint rmp_nonnegative check (rmp >= 0),
  trn int4 default 0 not null constraint trn_nonnegative check (trn >= 0),
  rmc int4 default 0 not null constraint rmc_nonnegative check (rmc >= 0),
  gm text,
  touched timestamp with time zone default current_timestamp not null,
  primary key (areaid, gameid)
);

-- Allow for fast access when searching by game and areaid.

create index crate_game on crate (gameid, areaid);

-- Allow for fast retrieval of when a player last played any game

create index crate_touched on crate (areaid, touched);

-- Allow access to it from the web server account

grant select on crate to public;

-- Related URL table
-- This is used to have links to other web sites related to a game displayed.

-- gameurls  Table name
-- gameid    Game the link applies to
-- url       The url for the link
-- comment   Text describing the relation
-- expires   When to stop displaying the link

create table gameurls (
  gameid text constraint bad_gameid references games,
  url text constraint bad_char_in_url check
    (url ~ '^[^[:cntrl:][:space:]]+$'),
  comment text not null constraint bad_char_in_comment check
    (comment ~ '^[^[:cntrl:][:space:]]+( [^[:cntrl:][:space:]]+)*$'),
  expires timestamp with time zone default 'infinity' not null,
  primary key (gameid, url)
);

-- Allow access to it from the web server account

grant select on gameurls to public;
