diff --git a/doc/specification/api.v1.raml b/doc/specification/api.v1.raml
new file mode 100644
index 0000000..44bd043
--- /dev/null
+++ b/doc/specification/api.v1.raml
@@ -0,0 +1,23 @@
+#%RAML 0.8
+ ---
+ title: YouYesYet API
+ baseUri: https://api.yyy.scot/{version}
+ version: v1
+
+
+/canvassers:
+ get:
+ put:
+
+/electors:
+ get:
+ /{id}:
+ get:
+ /{address_id}:
+ get:
+
+# Location isn't a real entity in the database, but it is a means of searching for
+# addresses and electors.
+/location:
+ /{lat}/{long}/{radius}:
+ get:
diff --git a/doc/specification/database.md b/doc/specification/database.md
new file mode 100644
index 0000000..6239008
--- /dev/null
+++ b/doc/specification/database.md
@@ -0,0 +1,235 @@
+# Database Specification
+
+Note that this is a work in progress. Read it in concert with the Entity-Relationship Diagram.
+
+Tables are listed in alphabetical order.
+
+## Address
+
+The postal address of a building which contains at least one dwelling at which electors are registered.
+
+ CREATE TABLE IF NOT EXISTS addresses (
+ id integer NOT NULL,
+ address character varying(256) NOT NULL,
+ postcode character varying(16),
+ phone character varying(16),
+ district_id integer,
+ latitude real,
+ longitude real
+ );
+
+
+## Authority
+
+An *oauth* authority which authenticates canvassers. *Note that* there will need to be substantially more in this table but I don't yet know what.
+
+ CREATE TABLE IF NOT EXISTS authorities (
+ id character varying(32) NOT NULL
+ );
+
+
+## Canvasser
+
+A user of the system.
+
+ CREATE TABLE IF NOT EXISTS canvassers (
+ id serial,
+ username character varying(32) NOT NULL,
+ fullname character varying(64) NOT NULL,
+ elector_id integer,
+ address_id integer NOT NULL,
+ phone character varying(16),
+ email character varying(128),
+ authority_id character varying(32) NOT NULL,
+ authorised boolean
+ );
+
+
+## District
+
+An electoral district.
+
+ CREATE TABLE IF NOT EXISTS districts (
+ id integer NOT NULL,
+ name character varying(64) NOT NULL
+ );
+
+
+## Dwelling
+
+A dwelling at which electors are registered. Most addresses obviously have only one dwelling, but in flatted buildings there will be multiple dwellings. The **sub\_address** field contains
+information to distinguish the dwelling, e.g. 'flat 2.1'.
+
+ CREATE TABLE IF NOT EXISTS dwellings (
+ id serial NOT NULL primary key,
+ address_id integer NOT NULL references addresses(id),
+ sub_address varchar(16)
+ );
+
+
+## Elector
+
+Someone entitled to cast a vote in the referendum.
+
+ CREATE TABLE IF NOT EXISTS electors (
+ id integer NOT NULL,
+ name character varying(64) NOT NULL,
+ dwelling_id integer NOT NULL,
+ phone character varying(16),
+ email character varying(128)
+ );
+
+
+## Followup Action
+
+An action performed by an issue expert in response to a followup request.
+
+ CREATE TABLE IF NOT EXISTS followupactions (
+ id integer NOT NULL,
+ request_id integer NOT NULL,
+ actor integer NOT NULL,
+ date timestamp with time zone DEFAULT now() NOT NULL,
+ notes text,
+ closed boolean
+ );
+
+
+## Followup Method
+
+A method for responding to a followup request; reference data.
+
+ CREATE TABLE IF NOT EXISTS followupmethods (
+ id character varying(32) NOT NULL
+ );
+
+ insert into followupmethods values ('Telephone');
+ insert into followupmethods values ('eMail');
+ insert into followupmethods values ('Post');
+
+
+## Followup Request
+
+A request recorded by a canvasser for an issue expert to contact an elector with regard to a particular issue.
+
+ CREATE TABLE IF NOT EXISTS followuprequests (
+ id integer NOT NULL,
+ elector_id integer NOT NULL,
+ visit_id integer NOT NULL,
+ issue_id character varying(32) NOT NULL,
+ method_id character varying(32) NOT NULL
+ );
+
+
+## Intention
+
+An intention, by an elector, to vote for an option; captured by a canvasser during a visit.
+
+ CREATE TABLE IF NOT EXISTS intentions (
+ id serial not null,
+ elector integer not null references elector(id),
+ option varchar(32) not null references option(id),
+ visit integer not null references visit(id),
+ date timestamp with time zone DEFAULT now() NOT NULL
+ );
+
+
+## Issue
+
+An issue which might affect electors' decisions regarding their intention.
+
+ CREATE TABLE IF NOT EXISTS issues (
+ id character varying(32) NOT NULL,
+ url character varying(256),
+ content varchar(1024),
+ current default false
+ );
+
+
+## Issue expertise
+
+Expertise of a canvasser able to use a method, in an issue.
+
+ CREATE TABLE IF NOT EXISTS issueexpertise (
+ canvasser_id integer NOT NULL,
+ issue_id character varying(32) NOT NULL,
+ method_id character varying(32) NOT NULL
+ );
+
+
+## Option
+
+An option for which an elector may have an intention to vote.
+
+ CREATE TABLE IF NOT EXISTS options (
+ id character varying(32) NOT NULL
+ );
+
+
+## Role
+
+A role (other than basic *Canvasser*) that a user may have in the system. Reference data.
+
+ create table if not exists roles (
+ id serial primary key,
+ name varchar(64) not null
+ );
+
+
+## Role Member
+
+Membership of a user (*Canvasser*) of an additional role; link table.
+
+ create table if not exists rolememberships (
+ role_id integer not null references roles(id),
+ canvasser_id integer not null references canvassers(id)
+ );
+
+
+## Team
+
+A team of canvassers in a locality who are known to one another and frequently
+canvas together.
+
+ create table if not exists teams (
+ id serial primary key,
+ name varchar(64) not null,
+ district_id integer not null references districts(id),
+ latitude real,
+ longitude real
+ );
+
+
+## Team Member
+
+Membership of a user (*Canvasser*) of a particular team. Canvassers may join multiple teams. Link table.
+
+ create table if not exists teammemberships (
+ team_id integer not null references teams(id),
+ canvasser_id integer not null references canvassers(id)
+ );
+
+
+## Team Organiser
+
+A relationship which defines a user (*Canvasser*) as an organiser of a team. A team may
+have more than one organiser. An organiser (if they also have the role 'Recruiter', which
+they often will have) may recruit additional Canvassers as members of their team, or
+accept applications by canvassers to join their team. An organiser may promote a member of
+the team to organiser of the team, and may also exclude a member from the team.
+
+ create table if not exists teamorganiserships (
+ team_id integer not null references teams(id),
+ canvasser_id integer not null references canvassers(id)
+ );
+
+
+## Visit
+
+A visit by a canvasser to an address on a date to solicit intentions from electors.
+
+ CREATE TABLE IF NOT EXISTS visits (
+ id integer NOT NULL,
+ address_id integer NOT NULL,
+ canvasser_id integer NOT NULL,
+ date timestamp with time zone DEFAULT now() NOT NULL
+ );
diff --git a/doc/specification/entity-relationship-diagram.svg b/doc/specification/entity-relationship-diagram.svg
index 0de6cf0..5585709 100644
--- a/doc/specification/entity-relationship-diagram.svg
+++ b/doc/specification/entity-relationship-diagram.svg
@@ -14,7 +14,7 @@
viewBox="0 0 1052.3622 744.09448"
id="svg2"
version="1.1"
- inkscape:version="0.91 r13725"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
sodipodi:docname="entity-relationship-diagram.svg">
@@ -25,14 +25,14 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
- inkscape:zoom="1.979899"
- inkscape:cx="833.70674"
- inkscape:cy="324.89697"
+ inkscape:zoom="0.9899495"
+ inkscape:cx="472.36875"
+ inkscape:cy="325.73865"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
inkscape:window-width="1920"
- inkscape:window-height="1058"
+ inkscape:window-height="1043"
inkscape:window-x="1920"
inkscape:window-y="0"
inkscape:window-maximized="1">
@@ -58,54 +58,49 @@
id="layer1"
transform="translate(0,-308.26772)">
+ x="16.060907"
+ y="312.36218" />
+ y="335.1539"
+ style="font-size:20px;line-height:1.25">
YouYesYet: Entity Relationship Diagram
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:20px;line-height:1.25;font-family:Arial;-inkscape-font-specification:'Arial Bold'">YouYesYet: Entity Relationship Diagram
District
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial">District
Addresss
+ sodipodi:role="line">Address
+ y="672.276" />
Elector
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial">Elector
+ y="553.07794" />
Authority
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial">Authority
+ y="553.07794" />
Visit
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial">Visit
+ y="794.50446" />
IssueIssueExpertiseIssueFollowup
+ y="915.72272" />
FollowupFollowupAction
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:1.25;font-family:Arial;-inkscape-font-specification:Arial">Action
+ transform="translate(170.18532,48.487322)">
+ transform="translate(682.33266,290.92393)">
+ id="g4369"
+ transform="translate(0,19.192898)">
@@ -402,7 +389,7 @@
+ transform="translate(170.18532,48.487322)">
+ id="g4402"
+ transform="translate(-168.69547,-222.23356)">
@@ -442,7 +430,7 @@
+ transform="translate(169.17517,171.72593)">
+ transform="translate(-170.71578,139.40105)">
Version: 0.2Version: 0.4Date: 20170315Date: 20170401Author: Simon BrookeCopyright: (c) 2016 Simon Brooke for Radical Independence CampaignIntroducedVisitedRecorded
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">RecordedRaised
+ style="font-style:italic;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:10px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Bold Italic';text-align:start;writing-mode:lr-tb;text-anchor:start">RaisedAuthenticates
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">Authenticates
Has
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">Has
About
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">About
AboutResponded to
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">Responded to
Expressed
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">Expressed
Contains
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">Contains
Resides at
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">Resides at
Requested
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">Requested
Performed
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">Performed
To
+ style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:10px;line-height:125%;font-family:Arial;-inkscape-font-specification:'Arial, Normal';text-align:start;writing-mode:lr-tb;text-anchor:start">To
+ y="672.276" />
Team
+ y="792.48413" />
+ y="562.36218"
+ style="font-size:20px;line-height:1.25">
Organiser-Organiser-shipTeamTeamMembership
+ id="tspan4385"
+ style="font-size:15px;line-height:1.25">Membership
HasHasof
+ style="font-size:10px;line-height:1.25">of
of
+ style="font-size:10px;line-height:1.25">of
+ transform="matrix(1,0,0,-1,171.72593,1625.9208)">
@@ -1035,41 +1001,41 @@
For
+ style="font-size:10px;line-height:1.25">For
+ y="672.276" />
RoleRoleMembership
+ id="tspan4383"
+ style="font-size:15px;line-height:1.25">Membership
+ transform="translate(0,-222.73864)">
Role
+ sodipodi:role="line"
+ style="font-size:15px;line-height:1.25">Role
+ id="g4338"
+ transform="translate(0,19.192898)">
Is
+ style="font-size:10px;line-height:1.25">Is
Includes
+ style="font-size:10px;line-height:1.25">Includes
+ transform="translate(341.43156,-222.73861)">
@@ -1159,5 +1124,73 @@
inkscape:connector-curvature="0" />
+
+
+ FollowupMethod
+
+
+
+
+
+
+ Address
diff --git a/doc/specification/scaling.md b/doc/specification/scaling.md
index bfc16de..8174799 100644
--- a/doc/specification/scaling.md
+++ b/doc/specification/scaling.md
@@ -22,7 +22,7 @@ Database reads are probably more infrequent. Each client will obviously need to
Mobile phones typically can have intermittent network access. The client must be able to buffer a queue of records to be stored, and must not prevent the user from moving on to the next doorstep just because the data from the last visit has not yet been stored. There should probably be some on-screen indication of when there is unsent buffered data.
-### Pattern of canvassing
+### Pattern of canvassing
Canvassing takes place typically between 6:30pm and 9:00pm on a weekday evening. There will be some canvassing outside this period, but not enough to create significant load. Canvassing will be higher on dry nights than on wet ones, and will probably ramp up through the campaign.
@@ -40,7 +40,7 @@ This means that the maximum number of transactions per second across Scotland is
700 transactions per second is not a very large number. We should be able to support this level of load on a single server. But what if we can't?
-## Spreading the load
+## Spreading the load
### Caching and memoizing
@@ -56,8 +56,38 @@ All this normalisation and memoisation reduces the number of read requests on th
Note that [clojure.core.memoize](https://github.com/clojure/core.memoize) provides us with functions to create both size-limited, least-recently-used caches and duration limited, time-to-live caches.
+### Searching the database for localities
+
At 56 degrees north there are 111,341 metres per degree of latitude, 62,392 metres per degree of longitude. So a 100 metre box is about 0.0016 degrees east-west and .0009 degrees north-south. If we simplify that slightly (and we don't need square boxes, we need units of area covering a group of people working together) then we can take .001 of a degree in either direction which is computationally cheap.
+Of course we could have a search query like this
+
+ select * from addresses
+ where latitude > 56.003
+ and latitude < 56.004
+ and longitude > -4.771
+ and longitude < -4.770;
+
+And it would work - but it would be computationally expensive. If we call each of these .001 x .001 roughly-rectangles a **locality**, then we can give every locality an integer index as follows
+
+ (defn locality-index
+ "Compute a locality for this `latitude`, `longitude` pair."
+ [latitude longitude]
+ (+
+ (* 10000 ;; left-shift the latitude component four digits
+ (integer
+ (* latitude 1000)))
+ (- ;; invert the sign of the longitude component, since
+ ;; we're interested in localities West of Greenwich.
+ (integer
+ (* longitude 1000)))))
+
+For values in Scotland, this gives us a number comfortable smaller than the maximum size of a 32 bit integer. Note that this isn't generally the case, so to adapt this software for use in Canada, for example, a more general solution would need to be chosen; but this will do for now. If we compute this index at the time the address is geocoded, then we can achieve the exact same results as the query given above with a much simpler query:
+
+ select * from address where locality = 560034770;
+
+If the locality field is indexed (which obviously it should be) this query becomes very cheap.
+
### Geographic sharding
Volunteers canvassing simultaneously in the same street or the same locality need to see in near real time which dwellings have been canvassed by other volunteers, otherwise we'll get the same households canvassed repeatedly, which wastes volunteer time and annoys voters. So they all need to be sending updates to, and receiving updates from, the same server. But volunteers canvassing in Aberdeen don't need to see in near real time what is happening in Edinburgh.
diff --git a/doc/specification/userspec.md b/doc/specification/userspec.md
index 3f494b1..efa9f59 100644
--- a/doc/specification/userspec.md
+++ b/doc/specification/userspec.md
@@ -1,4 +1,4 @@
-### YouYesYet: User-oriented specification
+# YouYesYet: User-oriented specification
## Overview
diff --git a/env/dev/cljs/youyesyet/dev.cljs b/env/dev/cljs/youyesyet/dev.cljs
index a246523..6394c48 100644
--- a/env/dev/cljs/youyesyet/dev.cljs
+++ b/env/dev/cljs/youyesyet/dev.cljs
@@ -1,5 +1,5 @@
-(ns ^:figwheel-no-load youyesyet.app
- (:require [youyesyet.core :as core]
+(ns ^:figwheel-no-load youyesyet.canvasser-app.app
+ (:require [youyesyet.canvasser-app.core :as core]
[devtools.core :as devtools]
[figwheel.client :as figwheel :include-macros true]))
diff --git a/env/prod/cljs/youyesyet/prod.cljs b/env/prod/cljs/youyesyet/prod.cljs
index ca12fd2..8cc0292 100644
--- a/env/prod/cljs/youyesyet/prod.cljs
+++ b/env/prod/cljs/youyesyet/prod.cljs
@@ -1,5 +1,5 @@
(ns youyesyet.app
- (:require [youyesyet.core :as core]))
+ (:require [youyesyet.canvasser-app.core :as core]))
;;ignore println statements in prod
(set! *print-fn* (fn [& _]))
diff --git a/project.clj b/project.clj
index 6108563..d671ac7 100644
--- a/project.clj
+++ b/project.clj
@@ -53,14 +53,20 @@
[migratus-lein "0.4.2"]
[org.clojars.punkisdead/lein-cucumber "1.0.5"]
[lein-cljsbuild "1.1.4"]
+ [lein-codox "0.10.3"]
[lein-uberwar "0.2.0"]
[lein-bower "0.5.1"]
- [lein-less "1.7.5"]]
+ [lein-less "1.7.5"]
+ [lein-codox "0.10.3"]]
:bower-dependencies [[leaflet "0.7.3"]]
:cucumber-feature-paths ["test/clj/features"]
+ :codox {:metadata {:doc "FIXME: write docs"}
+ :languages [:clojure :clojurescript]
+ :source-paths ["src/clj" "src/cljc" "src/cljs"]}
+
:uberwar
{:handler youyesyet.handler/app
:init youyesyet.handler/init
@@ -121,7 +127,7 @@
{:app
{:source-paths ["src/cljs" "src/cljc" "env/dev/cljs"]
:compiler
- {:main "youyesyet.app"
+ {:main "youyesyet.canvasser-app.app"
:asset-path "/js/out"
:output-to "target/cljsbuild/public/js/app.js"
:output-dir "target/cljsbuild/public/js/out"
diff --git a/resources/migrations/20161014170335-basic-setup.up.sql b/resources/migrations/20161014170335-basic-setup.up.sql
index 0719e57..4d74c06 100644
--- a/resources/migrations/20161014170335-basic-setup.up.sql
+++ b/resources/migrations/20161014170335-basic-setup.up.sql
@@ -48,14 +48,14 @@ SET client_min_messages = warning;
-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner:
--
-CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
+-- CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
--;;
--
-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner:
--
-COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
+-- COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
--;;
SET search_path = public, pg_catalog;
@@ -118,15 +118,16 @@ ALTER TABLE public.authorities OWNER TO youyesyet;
--
CREATE TABLE IF NOT EXISTS canvassers (
- id serial,
- username character varying(32) NOT NULL,
- fullname character varying(64) NOT NULL,
- elector_id integer,
- address_id integer NOT NULL,
- phone character varying(16),
- email character varying(128),
- authority_id character varying(32) NOT NULL,
- authorised boolean
+ id serial,
+ username character varying(32) NOT NULL,
+ fullname character varying(64) NOT NULL,
+ elector_id integer,
+ address_id integer NOT NULL,
+ phone character varying(16),
+ email character varying(128),
+ authority_id character varying(32) NOT NULL,
+ introduced_by int references canvassers(id),
+ authorised boolean
);
--;;
@@ -534,6 +535,8 @@ ALTER TABLE ONLY canvassers
ADD CONSTRAINT canvassers_elector_id_fkey FOREIGN KEY (elector_id) REFERENCES electors(id);
--;;
+create unique index canvassers_username_ix on canvassers (username);
+create unique index canvassers_email_ix on canvassers(email);
--
-- Name: electors_address_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: youyesyet
diff --git a/resources/migrations/20170401115900-basic-reference-data.down.sql b/resources/migrations/20170401115900-basic-reference-data.down.sql
new file mode 100644
index 0000000..a0b3aa1
--- /dev/null
+++ b/resources/migrations/20170401115900-basic-reference-data.down.sql
@@ -0,0 +1,17 @@
+-- this is just a teardown of everything set up in the corresponding .up.sql file
+
+delete from roles where name = 'Expert';
+delete from roles where name = 'Administrator';
+delete from roles where name = 'Recruiter';
+delete from roles where name = 'Organiser';
+delete from roles where name = 'Editor';
+
+alter table issues drop column content;
+alter table issues drop column current;
+
+delete from issues where id = 'Currency';
+delete from issues where id = 'Monarchy';
+delete from issues where id = 'Defence';
+
+delete from options where id = 'Yes';
+delete from options where id = 'No';
diff --git a/resources/migrations/20170401115900-basic-reference-data.up.sql b/resources/migrations/20170401115900-basic-reference-data.up.sql
new file mode 100644
index 0000000..26d3bf1
--- /dev/null
+++ b/resources/migrations/20170401115900-basic-reference-data.up.sql
@@ -0,0 +1,58 @@
+
+-- We don't explicitly instantiate the 'Canvasser' role since every user is
+-- deemed to be a canvasser.
+
+
+-- an 'Expert' is someone with expertise in one or more issues, who is
+-- trusted to discuss those issues in detail with electors.
+insert into roles (name) values ('Expert');
+
+
+-- an 'Administrator' is someone entitled to broadly alter reference data
+-- throughout the system.
+insert into roles (name) values ('Administrator');
+
+
+-- a 'Recruiter' is someone entitled to invite other people to become users
+-- ('Canvassers'). A Recruiter is entitled to lock the account of anyone they
+-- have recruited, recursively.
+insert into roles (name) values ('Recruiter');
+
+
+-- an 'Organiser' is someone who organises one or more local teams. An Organiser
+-- is entitled to exclude any Canvasser from any team they organise.
+insert into roles (name) values ('Organiser');
+
+
+-- an 'Editor' is someone entitled to add and edit issues.
+insert into roles (name) values ('Editor');
+
+-- issue text is local; there may still in addition be a further link to more
+-- information, but the basic issue text should be part of the issue record.
+-- The text should fit on a phone screen without scrolling, so is reasonably
+-- short.
+alter table issues add column content varchar(1024);
+
+-- an issue may be current or not current; when not current it is not deleted
+-- from the system but kept because it may become current again later. Only
+-- current issues are shown in the app. Typically not fewer than three and not
+-- more than about seven issues should be current at any time.
+alter table issues add column current boolean default false;
+
+insert into issues (id, content, current) values ('Currency',
+ 'Scotland could keep the Pound, or use the Euro. But we could also set up a new currency of our own.',
+ true);
+
+insert into issues (id, content, current) values ('Monarchy',
+ 'Scotland could keep the Queen. This is an issue to be decided after independence.',
+ true);
+
+insert into issues (id, content, current) values ('Defence',
+ 'Scotland will not have nuclear weapons, and will probably not choose to engage in far-off wars. But we could remain members of NATO.',
+ true);
+
+
+insert into options (id) values ('Yes');
+
+insert into options (id) values ('No');
+
diff --git a/resources/migrations/20170415102900-core-views.down.sql b/resources/migrations/20170415102900-core-views.down.sql
new file mode 100644
index 0000000..c576947
--- /dev/null
+++ b/resources/migrations/20170415102900-core-views.down.sql
@@ -0,0 +1,11 @@
+drop view if exists roles_by_canvasser;
+
+drop view if exists teams_by_canvasser;
+
+drop view if exists canvassers_by_team;
+
+drop view if exists canvassers_by_introducer;
+
+drop view if exists teams_by_organiser;
+
+drop view if exists organisers_by_team;
diff --git a/resources/migrations/20170415102900-core-views.up.sql b/resources/migrations/20170415102900-core-views.up.sql
new file mode 100644
index 0000000..53346a6
--- /dev/null
+++ b/resources/migrations/20170415102900-core-views.up.sql
@@ -0,0 +1,59 @@
+
+create view roles_by_canvasser as
+ select canvassers.id as canvasser, roles.name
+ from roles, rolememberships, canvassers
+ where roles.id = rolememberships.role_id
+ and canvassers.id = rolememberships.canvasser_id
+ and canvassers.authorised = true;
+
+create view teams_by_canvasser as
+ select canvassers.id as canvasser, teams.id, teams.name, teams.latitude, teams.longitude
+ from teams, teammemberships, canvassers
+ where teams.id = teammemberships.team_id
+ and canvassers.id = teammemberships.canvasser_id;
+
+create view canvassers_by_team as
+ select teams.id as team,
+ canvassers.id,
+ canvassers.username,
+ canvassers.fullname,
+ canvassers.email,
+ canvassers.phone
+ from teams, teammemberships, canvassers
+ where teams.id = teammemberships.team_id
+ and canvassers.id = teammemberships.canvasser_id
+ and canvassers.authorised = true;
+
+create view canvassers_by_introducer as
+ select introducers.id as introducer,
+ canvassers.id as canvasser,
+ canvassers.username,
+ canvassers.fullname,
+ canvassers.email,
+ canvassers.phone,
+ canvassers.authorised
+ from canvassers, canvassers as introducers
+ where introducers.id = canvassers.introduced_by;
+
+create view teams_by_organiser as
+ select canvassers.id as organiser,
+ teams.id,
+ teams.name,
+ teams.latitude,
+ teams.longitude
+ from teams, teamorganiserships, canvassers
+ where teams.id = teamorganiserships.team_id
+ and canvassers.id = teamorganiserships.canvasser_id
+ and canvassers.authorised = true;
+
+create view organisers_by_team as
+ select teams.id as team,
+ canvassers.id,
+ canvassers.username,
+ canvassers.fullname,
+ canvassers.email,
+ canvassers.phone
+ from teams, teamorganiserships, canvassers
+ where teams.id = teamorganiserships.team_id
+ and canvassers.id = teamorganiserships.canvasser_id
+ and canvassers.authorised = true;
diff --git a/resources/migrations/20170721084900-dwellings.down.sql b/resources/migrations/20170721084900-dwellings.down.sql
new file mode 100644
index 0000000..674fb1b
--- /dev/null
+++ b/resources/migrations/20170721084900-dwellings.down.sql
@@ -0,0 +1,69 @@
+--------------------------------------------------------------------------------
+----
+---- 20170721084900.up.sql: add dwellings table, to deal with flatted addresses.
+----
+---- This program is free software; you can redistribute it and/or
+---- modify it under the terms of the GNU General Public License
+---- as published by the Free Software Foundation; either version 2
+---- of the License, or (at your option) any later version.
+----
+---- This program is distributed in the hope that it will be useful,
+---- but WITHOUT ANY WARRANTY; without even the implied warranty of
+---- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+---- GNU General Public License for more details.
+----
+---- You should have received a copy of the GNU General Public License
+---- along with this program; if not, write to the Free Software
+---- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+---- USA.
+----
+---- Copyright (C) 2017 Simon Brooke for Radical Independence Campaign
+----
+--------------------------------------------------------------------------------
+----
+---- NOTE
+---- This file is essentially a Postgres schema dump of a database schema which was
+---- created with the function initdb! in the file src/clj/youyesyet/db/schema.clj.
+---- This file has then been mildly massaged to work with Migratus.
+---- Either this file or src/clj/youyesyet/db/schema.clj is redundant; schema.clj
+---- represents the older, Korma, way of doing things but does not readily allow
+---- for migrations; this file represents the newer Migratus/HugSQL way. I'm not
+---- certain which of these paths I'm going to go down.
+----
+--------------------------------------------------------------------------------
+
+alter table canvassers add column address_id integer references addresses(id);
+--;;
+alter table electors add column address_id integer references addresses(id);
+--;;
+alter table visits add column address_id integer references addresses(id);
+--;;
+
+update canvassers set address_id =
+ (select address_id from dwellings where id = canvassers.dwelling_id);
+--;;
+
+update electors set address_id =
+ (select address_id from dwellings where id = electors.dwelling_id);
+--;;
+
+update visits set address_id =
+ (select address_id from dwellings where id = visits.dwelling_id);
+--;;
+
+alter table canvassers alter column address_id set not null;
+--;;
+alter table electors alter column address_id set not null;
+--;;
+alter table visits alter column address_id set not null;
+--;;
+
+alter table canvassers drop column dwelling_id;
+--;;
+alter table electors drop column dwelling_id;
+--;;
+alter table visits drop column dwelling_id;
+--;;
+
+drop table if exists dwellings;
+--;;
diff --git a/resources/migrations/20170721084900-dwellings.up.sql b/resources/migrations/20170721084900-dwellings.up.sql
new file mode 100644
index 0000000..74e6a27
--- /dev/null
+++ b/resources/migrations/20170721084900-dwellings.up.sql
@@ -0,0 +1,87 @@
+--------------------------------------------------------------------------------
+----
+---- 20170721084900.up.sql: add dwellings table, to deal with flatted addresses.
+----
+---- This program is free software; you can redistribute it and/or
+---- modify it under the terms of the GNU General Public License
+---- as published by the Free Software Foundation; either version 2
+---- of the License, or (at your option) any later version.
+----
+---- This program is distributed in the hope that it will be useful,
+---- but WITHOUT ANY WARRANTY; without even the implied warranty of
+---- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+---- GNU General Public License for more details.
+----
+---- You should have received a copy of the GNU General Public License
+---- along with this program; if not, write to the Free Software
+---- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+---- USA.
+----
+---- Copyright (C) 2017 Simon Brooke for Radical Independence Campaign
+----
+--------------------------------------------------------------------------------
+----
+---- NOTE
+---- This file is essentially a Postgres schema dump of a database schema which was
+---- created with the function initdb! in the file src/clj/youyesyet/db/schema.clj.
+---- This file has then been mildly massaged to work with Migratus.
+---- Either this file or src/clj/youyesyet/db/schema.clj is redundant; schema.clj
+---- represents the older, Korma, way of doing things but does not readily allow
+---- for migrations; this file represents the newer Migratus/HugSQL way. I'm not
+---- certain which of these paths I'm going to go down.
+----
+--------------------------------------------------------------------------------
+
+CREATE TABLE IF NOT EXISTS dwellings (
+ id serial NOT NULL primary key,
+ address_id integer NOT NULL references addresses(id),
+ sub_address varchar(16)
+);
+--;;
+
+ALTER TABLE public.dwellings OWNER TO youyesyet;
+--;;
+
+INSERT INTO dwellings (address_id, sub_address)
+ SELECT DISTINCT id, '' FROM addresses;
+--;;
+
+alter table canvassers add column dwelling_id integer references dwellings(id);
+--;;
+alter table electors add column dwelling_id integer references dwellings(id);
+--;;
+alter table visits add column dwelling_id integer references dwellings(id);
+--;;
+
+update canvassers set dwelling_id =
+ (select id from dwellings where address_id = canvassers.address_id);
+--;;
+
+update electors set dwelling_id =
+ (select id from dwellings where address_id = electors.address_id);
+--;;
+
+update visits set dwelling_id =
+ (select id from dwellings where address_id = visits.address_id);
+--;;
+
+alter table canvassers alter column dwelling_id set not null;
+--;;
+alter table electors alter column dwelling_id set not null;
+--;;
+alter table visits alter column dwelling_id set not null;
+--;;
+
+alter table canvassers drop constraint canvassers_address_id_fkey;
+--;;
+alter table electors drop constraint electors_address_id_fkey;
+--;;
+alter table visits drop constraint visits_address_id_fkey;
+--;;
+
+alter table canvassers drop column address_id;
+--;;
+alter table electors drop column address_id;
+--;;
+alter table visits drop column address_id;
+--;;
diff --git a/resources/migrations/20180316110100-intentions-and-options.down.sql b/resources/migrations/20180316110100-intentions-and-options.down.sql
new file mode 100644
index 0000000..84c98b8
--- /dev/null
+++ b/resources/migrations/20180316110100-intentions-and-options.down.sql
@@ -0,0 +1,24 @@
+--------------------------------------------------------------------------------
+----
+---- 20180316110100intentions-and-options.down.sql: remove intentions and options
+----
+---- This program is free software; you can redistribute it and/or
+---- modify it under the terms of the GNU General Public License
+---- as published by the Free Software Foundation; either version 2
+---- of the License, or (at your option) any later version.
+----
+---- This program is distributed in the hope that it will be useful,
+---- but WITHOUT ANY WARRANTY; without even the implied warranty of
+---- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+---- GNU General Public License for more details.
+----
+---- You should have received a copy of the GNU General Public License
+---- along with this program; if not, write to the Free Software
+---- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+---- USA.
+----
+---- Copyright (C) 2016 Simon Brooke for Radical Independence Campaign
+----
+--------------------------------------------------------------------------------
+
+drop table intentions;
diff --git a/resources/migrations/20180316110100-intentions-and-options.up.sql b/resources/migrations/20180316110100-intentions-and-options.up.sql
new file mode 100644
index 0000000..0736740
--- /dev/null
+++ b/resources/migrations/20180316110100-intentions-and-options.up.sql
@@ -0,0 +1,30 @@
+--------------------------------------------------------------------------------
+----
+---- 20180316110100intentions-and-options.up.sql: add intentions and options
+----
+---- This program is free software; you can redistribute it and/or
+---- modify it under the terms of the GNU General Public License
+---- as published by the Free Software Foundation; either version 2
+---- of the License, or (at your option) any later version.
+----
+---- This program is distributed in the hope that it will be useful,
+---- but WITHOUT ANY WARRANTY; without even the implied warranty of
+---- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+---- GNU General Public License for more details.
+----
+---- You should have received a copy of the GNU General Public License
+---- along with this program; if not, write to the Free Software
+---- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+---- USA.
+----
+---- Copyright (C) 2016 Simon Brooke for Radical Independence Campaign
+----
+--------------------------------------------------------------------------------
+
+CREATE TABLE IF NOT EXISTS intentions (
+ visit_id int not null references visits(id) on delete no action,
+ elector_id int not null references electors(id) on delete no action,
+ option_id varchar(32) not null references options(id) on delete no action
+ );
+
+ALTER TABLE intentions owner to youyesyet;
diff --git a/resources/migrations/20180317170000-gender.down.sql b/resources/migrations/20180317170000-gender.down.sql
new file mode 100644
index 0000000..5eac53a
--- /dev/null
+++ b/resources/migrations/20180317170000-gender.down.sql
@@ -0,0 +1,3 @@
+alter table electors drop column gender;
+
+drop table genders;
diff --git a/resources/migrations/20180317170000-gender.up.sql b/resources/migrations/20180317170000-gender.up.sql
new file mode 100644
index 0000000..9512ea2
--- /dev/null
+++ b/resources/migrations/20180317170000-gender.up.sql
@@ -0,0 +1,11 @@
+create table genders (
+ id varchar(32) not null primary key
+);
+
+-- genders is reference data
+insert into genders values ('Female');
+insert into genders values ('Male');
+insert into genders values ('Non-binary');
+insert into genders values ('Unknown');
+
+alter table electors add column gender varchar(32) references genders(id) default 'Unknown';
diff --git a/resources/migrations/20180317170907-reference-data.down.sql b/resources/migrations/20180317170907-reference-data.down.sql
new file mode 100644
index 0000000..ed67940
--- /dev/null
+++ b/resources/migrations/20180317170907-reference-data.down.sql
@@ -0,0 +1,10 @@
+delete from options where id = 'Yes';
+
+delete from options where id = 'No';
+
+delete from issues where id = 'Currency';
+
+delete from issues where id = 'Monarchy';
+
+delete from issues where id = 'Defence';
+
diff --git a/resources/migrations/20180317170907-reference-data.up.sql b/resources/migrations/20180317170907-reference-data.up.sql
new file mode 100644
index 0000000..0ab0f4c
--- /dev/null
+++ b/resources/migrations/20180317170907-reference-data.up.sql
@@ -0,0 +1,10 @@
+insert into options values ('Yes');
+
+insert into options values ('No');
+
+
+insert into issues (id, url) values ('Currency', 'https://www.yyy.scot/wiki/issues/Currency');
+
+insert into issues (id, url) values ('Monarchy', 'https://www.yyy.scot/wiki/issues/Monarchy');
+
+insert into issues (id, url) values ('Defence', 'https://www.yyy.scot/wiki/issues/Defence');
diff --git a/resources/migrations/20180317175047-test-data.down.sql b/resources/migrations/20180317175047-test-data.down.sql
new file mode 100644
index 0000000..4a5583c
--- /dev/null
+++ b/resources/migrations/20180317175047-test-data.down.sql
@@ -0,0 +1,3 @@
+delete from addresses where id < = 4;
+
+delete from electors where id <= 10;
diff --git a/resources/migrations/20180317175047-test-data.up.sql b/resources/migrations/20180317175047-test-data.up.sql
new file mode 100644
index 0000000..0cc4b20
--- /dev/null
+++ b/resources/migrations/20180317175047-test-data.up.sql
@@ -0,0 +1,41 @@
+insert into addresses (id, address, postcode, latitude, longitude)
+values (1, '13 Imaginary Terrace, IM1 3TE', 'IM1 3TE', 55.8253043, -4.2569057);
+
+insert into addresses (id, address, postcode, latitude, longitude)
+values (2, '15 Imaginary Terrace, IM1 3TE', 'IM1 3TE', 55.8252354, -4.2572778);
+
+insert into addresses (id, address, postcode, latitude, longitude)
+values (3, '17 Imaginary Terrace, IM1 3TE', 'IM1 3TE', 55.825166, -4.257026);
+
+insert into addresses (id, address, postcode, latitude, longitude)
+values (4, '19 Imaginary Terrace, IM1 3TE', 'IM1 3TE', 55.8250695, -4.2570239);
+
+insert into electors (id, name, address_id, gender)
+values (1, 'Alan Anderson', 1, 'Male');
+
+insert into electors (id, name, address_id, gender)
+values (2, 'Ann Anderson', 1, 'Female');
+
+insert into electors (id, name, address_id, gender)
+values (3, 'Alex Anderson', 1, 'Non-binary');
+
+insert into electors (id, name, address_id)
+values (4, 'Andy Anderson', 1);
+
+insert into electors (id, name, address_id, gender)
+values (5, 'Beryl Brown', 2, 'Female');
+
+insert into electors (id, name, address_id, gender)
+values (6, 'Betty Black', 2, 'Female');
+
+insert into electors (id, name, address_id, gender)
+values (7, 'Catriona Crathie', 3, 'Female');
+
+insert into electors (id, name, address_id, gender)
+values (8, 'Colin Caruthers', 3, 'Male');
+
+insert into electors (id, name, address_id, gender)
+values (9, 'Calum Crathie', 3, 'Unknown');
+
+insert into electors (id, name, address_id, gender)
+values (10, 'David Dewar', 4, 'Male');
diff --git a/resources/migrations/20180408124500-reference-data.down.sql b/resources/migrations/20180408124500-reference-data.down.sql
new file mode 100644
index 0000000..68bada9
--- /dev/null
+++ b/resources/migrations/20180408124500-reference-data.down.sql
@@ -0,0 +1 @@
+alter table issues drop column current;
diff --git a/resources/migrations/20180408124500-reference-data.up.sql b/resources/migrations/20180408124500-reference-data.up.sql
new file mode 100644
index 0000000..aaae234
--- /dev/null
+++ b/resources/migrations/20180408124500-reference-data.up.sql
@@ -0,0 +1,2 @@
+alter table issues add column current boolean default true;
+
diff --git a/resources/migrations/20180526162051-dwellings.down.sql b/resources/migrations/20180526162051-dwellings.down.sql
new file mode 100644
index 0000000..ab91769
--- /dev/null
+++ b/resources/migrations/20180526162051-dwellings.down.sql
@@ -0,0 +1,8 @@
+alter table electors
+ add column address_id references addresses on delete no action;
+
+update electors
+ set address_id =
+ (select address_id
+ from dwellings
+ where dwellings.id electors.dwelling_id);
diff --git a/resources/migrations/20180526162051-dwellings.up.sql b/resources/migrations/20180526162051-dwellings.up.sql
new file mode 100644
index 0000000..41e1a6e
--- /dev/null
+++ b/resources/migrations/20180526162051-dwellings.up.sql
@@ -0,0 +1,11 @@
+CREATE TABLE dwellings
+(
+ id INT NOT NULL PRIMARY KEY,
+ address_id INT NOT NULL references addresses on delete no action,
+ sub_address VARCHAR( 32)
+);
+
+alter table electors
+ add column dwelling_id int references dwellings on delete no action;
+
+alter table electors drop column address_id;
diff --git a/resources/public/css/yyy-site.css b/resources/public/css/yyy-site.css
index 5387c7a..6f9b521 100644
--- a/resources/public/css/yyy-site.css
+++ b/resources/public/css/yyy-site.css
@@ -46,6 +46,7 @@
#nav-menu {
margin: 0;
padding: 0;
+ width: 100%;
}
#nav menu li {
diff --git a/resources/sql/queries.auto.sql b/resources/sql/queries.auto.sql
new file mode 100644
index 0000000..a80bf0f
--- /dev/null
+++ b/resources/sql/queries.auto.sql
@@ -0,0 +1,836 @@
+-- File queries.sql
+-- autogenerated by adl.to-hugsql-queries at
+-- 2018-05-26T15:03:25.295Z
+-- See [Application Description Language](https://github.com/simon-brooke/adl).
+
+
+
+-- :name create-address! :! :n
+-- :doc creates a new address record
+INSERT INTO addresses (address,
+ postcode,
+ phone,
+ district_id,
+ latitude,
+ longitude)
+VALUES (:address,
+ :postcode,
+ :phone,
+ :district_id,
+ :latitude,
+ :longitude)
+returning id
+
+-- :name create-authority! :! :n
+-- :doc creates a new authority record
+INSERT INTO authorities (id)
+VALUES (:id)
+returning id
+
+-- :name create-canvasser! :! :n
+-- :doc creates a new canvasser record
+INSERT INTO canvassers (username,
+ fullname,
+ elector_id,
+ address_id,
+ phone,
+ email,
+ authority_id,
+ authorised)
+VALUES (:username,
+ :fullname,
+ :elector_id,
+ :address_id,
+ :phone,
+ :email,
+ :authority_id,
+ :authorised)
+returning id
+
+-- :name create-district! :! :n
+-- :doc creates a new district record
+INSERT INTO districts (name)
+VALUES (:name)
+returning id
+
+-- :name create-dwelling! :! :n
+-- :doc creates a new dwelling record
+INSERT INTO dwellings (address_id,
+ sub-address)
+VALUES (:address_id,
+ :sub-address)
+returning id
+
+-- :name create-elector! :! :n
+-- :doc creates a new elector record
+INSERT INTO electors (name,
+ dwelling_id,
+ phone,
+ email,
+ gender)
+VALUES (:name,
+ :dwelling_id,
+ :phone,
+ :email,
+ :gender)
+returning id
+
+-- :name create-followupaction! :! :n
+-- :doc creates a new followupaction record
+INSERT INTO followupactions (request_id,
+ actor,
+ date,
+ notes,
+ closed)
+VALUES (:request_id,
+ :actor,
+ :date,
+ :notes,
+ :closed)
+returning id
+
+-- :name create-followupmethod! :! :n
+-- :doc creates a new followupmethod record
+INSERT INTO followupmethods (id)
+VALUES (:id)
+returning id
+
+-- :name create-followuprequest! :! :n
+-- :doc creates a new followuprequest record
+INSERT INTO followuprequests (elector_id,
+ visit_id,
+ issue_id,
+ method_id)
+VALUES (:elector_id,
+ :visit_id,
+ :issue_id,
+ :method_id)
+returning id
+
+-- :name create-gender! :! :n
+-- :doc creates a new gender record
+INSERT INTO genders (id)
+VALUES (:id)
+returning id
+
+-- :name create-intention! :! :n
+-- :doc creates a new intention record
+INSERT INTO intentions (visit_id,
+ elector_id,
+ option_id)
+VALUES (:visit_id,
+ :elector_id,
+ :option_id)
+returning Id
+
+-- :name create-issue! :! :n
+-- :doc creates a new issue record
+INSERT INTO issues (url,
+ current,
+ id)
+VALUES (:url,
+ :current,
+ :id)
+returning id
+
+-- :name create-issueexpertis! :! :n
+-- :doc creates a new issueexpertis record
+INSERT INTO issueexpertise (canvasser_id,
+ issue_id,
+ method_id)
+VALUES (:canvasser_id,
+ :issue_id,
+ :method_id)
+returning Id
+
+-- :name create-option! :! :n
+-- :doc creates a new option record
+INSERT INTO options (id)
+VALUES (:id)
+returning id
+
+-- :name create-role! :! :n
+-- :doc creates a new role record
+INSERT INTO roles (name)
+VALUES (:name)
+returning id
+
+-- :name create-rolemembership! :! :n
+-- :doc creates a new rolemembership record
+INSERT INTO rolememberships (role_id,
+ canvasser_id)
+VALUES (:role_id,
+ :canvasser_id)
+returning Id
+
+-- :name create-team! :! :n
+-- :doc creates a new team record
+INSERT INTO teams (name,
+ district_id,
+ latitude,
+ longitude)
+VALUES (:name,
+ :district_id,
+ :latitude,
+ :longitude)
+returning id
+
+-- :name create-teammembership! :! :n
+-- :doc creates a new teammembership record
+INSERT INTO teammemberships (team_id,
+ canvasser_id)
+VALUES (:team_id,
+ :canvasser_id)
+returning Id
+
+-- :name create-teamorganisership! :! :n
+-- :doc creates a new teamorganisership record
+INSERT INTO teamorganiserships (team_id,
+ canvasser_id)
+VALUES (:team_id,
+ :canvasser_id)
+returning Id
+
+-- :name create-visit! :! :n
+-- :doc creates a new visit record
+INSERT INTO visits (address_id,
+ canvasser_id,
+ date)
+VALUES (:address_id,
+ :canvasser_id,
+ :date)
+returning id
+
+-- :name delete-address! :! :n
+-- :doc updates an existing address record
+DELETE FROM addresses
+WHERE addresses.id = :id
+
+-- :name delete-authority! :! :n
+-- :doc updates an existing authority record
+DELETE FROM authorities
+WHERE authorities.id = :id
+
+-- :name delete-canvasser! :! :n
+-- :doc updates an existing canvasser record
+DELETE FROM canvassers
+WHERE canvassers.id = :id
+
+-- :name delete-district! :! :n
+-- :doc updates an existing district record
+DELETE FROM districts
+WHERE districts.id = :id
+
+-- :name delete-dwelling! :! :n
+-- :doc updates an existing dwelling record
+DELETE FROM dwellings
+WHERE dwellings.id = :id
+
+-- :name delete-elector! :! :n
+-- :doc updates an existing elector record
+DELETE FROM electors
+WHERE electors.id = :id
+
+-- :name delete-followupaction! :! :n
+-- :doc updates an existing followupaction record
+DELETE FROM followupactions
+WHERE followupactions.id = :id
+
+-- :name delete-followupmethod! :! :n
+-- :doc updates an existing followupmethod record
+DELETE FROM followupmethods
+WHERE followupmethods.id = :id
+
+-- :name delete-followuprequest! :! :n
+-- :doc updates an existing followuprequest record
+DELETE FROM followuprequests
+WHERE followuprequests.id = :id
+
+-- :name delete-gender! :! :n
+-- :doc updates an existing gender record
+DELETE FROM genders
+WHERE genders.id = :id
+
+-- :name delete-intention! :! :n
+-- :doc updates an existing intention record
+DELETE FROM intentions
+WHERE intentions.Id = :Id
+
+-- :name delete-issue! :! :n
+-- :doc updates an existing issue record
+DELETE FROM issues
+WHERE issues.id = :id
+
+-- :name delete-issueexpertis! :! :n
+-- :doc updates an existing issueexpertis record
+DELETE FROM issueexpertise
+WHERE issueexpertise.Id = :Id
+
+-- :name delete-option! :! :n
+-- :doc updates an existing option record
+DELETE FROM options
+WHERE options.id = :id
+
+-- :name delete-role! :! :n
+-- :doc updates an existing role record
+DELETE FROM roles
+WHERE roles.id = :id
+
+-- :name delete-rolemembership! :! :n
+-- :doc updates an existing rolemembership record
+DELETE FROM rolememberships
+WHERE rolememberships.Id = :Id
+
+-- :name delete-team! :! :n
+-- :doc updates an existing team record
+DELETE FROM teams
+WHERE teams.id = :id
+
+-- :name delete-teammembership! :! :n
+-- :doc updates an existing teammembership record
+DELETE FROM teammemberships
+WHERE teammemberships.Id = :Id
+
+-- :name delete-teamorganisership! :! :n
+-- :doc updates an existing teamorganisership record
+DELETE FROM teamorganiserships
+WHERE teamorganiserships.Id = :Id
+
+-- :name delete-visit! :! :n
+-- :doc updates an existing visit record
+DELETE FROM visits
+WHERE visits.id = :id
+
+-- :name get-address :? :1
+-- :doc selects an existing address record
+SELECT * FROM addresses
+WHERE addresses.id = :id
+ORDER BY addresses.address,
+ addresses.postcode,
+ addresses.id
+
+-- :name get-authority :? :1
+-- :doc selects an existing authority record
+SELECT * FROM authorities
+WHERE authorities.id = :id
+
+-- :name get-canvasser :? :1
+-- :doc selects an existing canvasser record
+SELECT * FROM canvassers
+WHERE canvassers.id = :id
+ORDER BY canvassers.username,
+ canvassers.fullname,
+ canvassers.email,
+ canvassers.id
+
+-- :name get-district :? :1
+-- :doc selects an existing district record
+SELECT * FROM districts
+WHERE districts.id = :id
+ORDER BY districts.name,
+ districts.id
+
+-- :name get-dwelling :? :1
+-- :doc selects an existing dwelling record
+SELECT * FROM dwellings
+WHERE dwellings.id = :id
+
+-- :name get-elector :? :1
+-- :doc selects an existing elector record
+SELECT * FROM electors
+WHERE electors.id = :id
+ORDER BY electors.name,
+ electors.phone,
+ electors.email,
+ electors.id
+
+-- :name get-followupaction :? :1
+-- :doc selects an existing followupaction record
+SELECT * FROM followupactions
+WHERE followupactions.id = :id
+
+-- :name get-followupmethod :? :1
+-- :doc selects an existing followupmethod record
+SELECT * FROM followupmethods
+WHERE followupmethods.id = :id
+
+-- :name get-followuprequest :? :1
+-- :doc selects an existing followuprequest record
+SELECT * FROM followuprequests
+WHERE followuprequests.id = :id
+
+-- :name get-gender :? :1
+-- :doc selects an existing gender record
+SELECT * FROM genders
+WHERE genders.id = :id
+
+-- :name get-intention :? :1
+-- :doc selects an existing intention record
+SELECT * FROM intentions
+WHERE intentions.Id = :Id
+
+-- :name get-issue :? :1
+-- :doc selects an existing issue record
+SELECT * FROM issues
+WHERE issues.id = :id
+
+-- :name get-issueexpertis :? :1
+-- :doc selects an existing issueexpertis record
+SELECT * FROM issueexpertise
+WHERE issueexpertise.Id = :Id
+
+-- :name get-option :? :1
+-- :doc selects an existing option record
+SELECT * FROM options
+WHERE options.id = :id
+
+-- :name get-role :? :1
+-- :doc selects an existing role record
+SELECT * FROM roles
+WHERE roles.id = :id
+ORDER BY roles.name,
+ roles.id
+
+-- :name get-rolemembership :? :1
+-- :doc selects an existing rolemembership record
+SELECT * FROM rolememberships
+WHERE rolememberships.Id = :Id
+
+-- :name get-team :? :1
+-- :doc selects an existing team record
+SELECT * FROM teams
+WHERE teams.id = :id
+ORDER BY teams.name,
+ teams.id
+
+-- :name get-teammembership :? :1
+-- :doc selects an existing teammembership record
+SELECT * FROM teammemberships
+WHERE teammemberships.Id = :Id
+
+-- :name get-teamorganisership :? :1
+-- :doc selects an existing teamorganisership record
+SELECT * FROM teamorganiserships
+WHERE teamorganiserships.Id = :Id
+
+-- :name get-visit :? :1
+-- :doc selects an existing visit record
+SELECT * FROM visits
+WHERE visits.id = :id
+
+-- :name list-addresses :? :*
+-- :doc lists all existing address records
+SELECT * FROM addresses
+ORDER BY addresses.address,
+ addresses.postcode,
+ addresses.id
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-addresses-by-district :? :*
+-- :doc lists all existing address records related to a given district
+SELECT *
+FROM addresses
+WHERE addresses.district_id = :id
+ORDER BY addresses.address,
+ addresses.postcode,
+ addresses.id
+
+-- :name list-authorities :? :*
+-- :doc lists all existing authority records
+SELECT * FROM authorities
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-canvassers :? :*
+-- :doc lists all existing canvasser records
+SELECT * FROM canvassers
+ORDER BY canvassers.username,
+ canvassers.fullname,
+ canvassers.email,
+ canvassers.id
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-canvassers-by-address :? :*
+-- :doc lists all existing canvasser records related to a given address
+SELECT *
+FROM canvassers
+WHERE canvassers.address_id = :id
+ORDER BY canvassers.username,
+ canvassers.fullname,
+ canvassers.email,
+ canvassers.id
+
+-- :name list-canvassers-by-authority :? :*
+-- :doc lists all existing canvasser records related to a given authority
+SELECT *
+FROM canvassers
+WHERE canvassers.authority_id = :id
+ORDER BY canvassers.username,
+ canvassers.fullname,
+ canvassers.email,
+ canvassers.id
+
+-- :name list-canvassers-by-elector :? :*
+-- :doc lists all existing canvasser records related to a given elector
+SELECT *
+FROM canvassers
+WHERE canvassers.elector_id = :id
+ORDER BY canvassers.username,
+ canvassers.fullname,
+ canvassers.email,
+ canvassers.id
+
+-- :name list-districts :? :*
+-- :doc lists all existing district records
+SELECT * FROM districts
+ORDER BY districts.name,
+ districts.id
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-dwellings :? :*
+-- :doc lists all existing dwelling records
+SELECT * FROM dwellings
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-dwellings-by-addres :? :*
+-- :doc lists all existing dwelling records related to a given addres
+SELECT *
+FROM dwellings
+WHERE dwellings.address_id = :id
+
+-- :name list-electors :? :*
+-- :doc lists all existing elector records
+SELECT * FROM electors
+ORDER BY electors.name,
+ electors.phone,
+ electors.email,
+ electors.id
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-electors-by-dwelling :? :*
+-- :doc lists all existing elector records related to a given dwelling
+SELECT *
+FROM electors
+WHERE electors.dwelling_id = :id
+ORDER BY electors.name,
+ electors.phone,
+ electors.email,
+ electors.id
+
+-- :name list-electors-by-gender :? :*
+-- :doc lists all existing elector records related to a given gender
+SELECT *
+FROM electors
+WHERE electors.gender = :id
+ORDER BY electors.name,
+ electors.phone,
+ electors.email,
+ electors.id
+
+-- :name list-followupactions :? :*
+-- :doc lists all existing followupaction records
+SELECT * FROM followupactions
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-followupactions-by-canvasser :? :*
+-- :doc lists all existing followupaction records related to a given canvasser
+SELECT *
+FROM followupactions
+WHERE followupactions.actor = :id
+
+-- :name list-followupactions-by-followuprequest :? :*
+-- :doc lists all existing followupaction records related to a given followuprequest
+SELECT *
+FROM followupactions
+WHERE followupactions.request_id = :id
+
+-- :name list-followupmethods :? :*
+-- :doc lists all existing followupmethod records
+SELECT * FROM followupmethods
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-followuprequests :? :*
+-- :doc lists all existing followuprequest records
+SELECT * FROM followuprequests
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-followuprequests-by-elector :? :*
+-- :doc lists all existing followuprequest records related to a given elector
+SELECT *
+FROM followuprequests
+WHERE followuprequests.elector_id = :id
+
+-- :name list-followuprequests-by-followupmethod :? :*
+-- :doc lists all existing followuprequest records related to a given followupmethod
+SELECT *
+FROM followuprequests
+WHERE followuprequests.method_id = :id
+
+-- :name list-followuprequests-by-issue :? :*
+-- :doc lists all existing followuprequest records related to a given issue
+SELECT *
+FROM followuprequests
+WHERE followuprequests.issue_id = :id
+
+-- :name list-followuprequests-by-visit :? :*
+-- :doc lists all existing followuprequest records related to a given visit
+SELECT *
+FROM followuprequests
+WHERE followuprequests.visit_id = :id
+
+-- :name list-genders :? :*
+-- :doc lists all existing gender records
+SELECT * FROM genders
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-intentions :? :*
+-- :doc lists all existing intention records
+SELECT * FROM intentions
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-intentions-by-elector :? :*
+-- :doc lists all existing intention records related to a given elector
+SELECT *
+FROM intentions
+WHERE intentions.elector_id = :id
+
+-- :name list-intentions-by-option :? :*
+-- :doc lists all existing intention records related to a given option
+SELECT *
+FROM intentions
+WHERE intentions.option_id = :id
+
+-- :name list-intentions-by-visit :? :*
+-- :doc lists all existing intention records related to a given visit
+SELECT *
+FROM intentions
+WHERE intentions.visit_id = :id
+
+-- :name list-issueexpertise :? :*
+-- :doc lists all existing issueexpertis records
+SELECT * FROM issueexpertise
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-issueexpertise-by-canvasser :? :*
+-- :doc lists all existing issueexpertis records related to a given canvasser
+SELECT *
+FROM issueexpertise
+WHERE issueexpertise.canvasser_id = :id
+
+-- :name list-issueexpertise-by-followupmethod :? :*
+-- :doc lists all existing issueexpertis records related to a given followupmethod
+SELECT *
+FROM issueexpertise
+WHERE issueexpertise.method_id = :id
+
+-- :name list-issueexpertise-by-issue :? :*
+-- :doc lists all existing issueexpertis records related to a given issue
+SELECT *
+FROM issueexpertise
+WHERE issueexpertise.issue_id = :id
+
+-- :name list-issues :? :*
+-- :doc lists all existing issue records
+SELECT * FROM issues
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-options :? :*
+-- :doc lists all existing option records
+SELECT * FROM options
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-rolememberships :? :*
+-- :doc lists all existing rolemembership records
+SELECT * FROM rolememberships
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-rolememberships-by-canvasser :? :*
+-- :doc lists all existing rolemembership records related to a given canvasser
+SELECT *
+FROM rolememberships
+WHERE rolememberships.canvasser_id = :id
+
+-- :name list-rolememberships-by-role :? :*
+-- :doc lists all existing rolemembership records related to a given role
+SELECT *
+FROM rolememberships
+WHERE rolememberships.role_id = :id
+
+-- :name list-roles :? :*
+-- :doc lists all existing role records
+SELECT * FROM roles
+ORDER BY roles.name,
+ roles.id
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-teammemberships :? :*
+-- :doc lists all existing teammembership records
+SELECT * FROM teammemberships
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-teammemberships-by-canvasser :? :*
+-- :doc lists all existing teammembership records related to a given canvasser
+SELECT *
+FROM teammemberships
+WHERE teammemberships.canvasser_id = :id
+
+-- :name list-teammemberships-by-team :? :*
+-- :doc lists all existing teammembership records related to a given team
+SELECT *
+FROM teammemberships
+WHERE teammemberships.team_id = :id
+
+-- :name list-teamorganiserships :? :*
+-- :doc lists all existing teamorganisership records
+SELECT * FROM teamorganiserships
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-teamorganiserships-by-canvasser :? :*
+-- :doc lists all existing teamorganisership records related to a given canvasser
+SELECT *
+FROM teamorganiserships
+WHERE teamorganiserships.canvasser_id = :id
+
+-- :name list-teamorganiserships-by-team :? :*
+-- :doc lists all existing teamorganisership records related to a given team
+SELECT *
+FROM teamorganiserships
+WHERE teamorganiserships.team_id = :id
+
+-- :name list-teams :? :*
+-- :doc lists all existing team records
+SELECT * FROM teams
+ORDER BY teams.name,
+ teams.id
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-teams-by-district :? :*
+-- :doc lists all existing team records related to a given district
+SELECT *
+FROM teams
+WHERE teams.district_id = :id
+ORDER BY teams.name,
+ teams.id
+
+-- :name list-visits :? :*
+-- :doc lists all existing visit records
+SELECT * FROM visits
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name list-visits-by-address :? :*
+-- :doc lists all existing visit records related to a given address
+SELECT *
+FROM visits
+WHERE visits.address_id = :id
+
+-- :name list-visits-by-canvasser :? :*
+-- :doc lists all existing visit records related to a given canvasser
+SELECT *
+FROM visits
+WHERE visits.canvasser_id = :id
+
+-- :name search-strings-address :? :1
+-- :doc selects existing address records having any string field matching `:pattern` by substring match
+SELECT * FROM addresses
+WHERE
+address LIKE '%:pattern%'
+ OR phone LIKE '%:pattern%'
+ORDER BY addresses.address,
+ addresses.postcode,
+ addresses.id
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name search-strings-canvasser :? :1
+-- :doc selects existing canvasser records having any string field matching `:pattern` by substring match
+SELECT * FROM canvassers
+WHERE
+username LIKE '%:pattern%'
+ OR fullname LIKE '%:pattern%'
+ OR phone LIKE '%:pattern%'
+ OR email LIKE '%:pattern%'
+ORDER BY canvassers.username,
+ canvassers.fullname,
+ canvassers.email,
+ canvassers.id
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name search-strings-district :? :1
+-- :doc selects existing district records having any string field matching `:pattern` by substring match
+SELECT * FROM districts
+WHERE
+name LIKE '%:pattern%'
+ORDER BY districts.name,
+ districts.id
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name search-strings-dwelling :? :1
+-- :doc selects existing dwelling records having any string field matching `:pattern` by substring match
+SELECT * FROM dwellings
+WHERE
+sub-address LIKE '%:pattern%'
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name search-strings-elector :? :1
+-- :doc selects existing elector records having any string field matching `:pattern` by substring match
+SELECT * FROM electors
+WHERE
+name LIKE '%:pattern%'
+ OR phone LIKE '%:pattern%'
+ OR email LIKE '%:pattern%'
+ORDER BY electors.name,
+ electors.phone,
+ electors.email,
+ electors.id
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name search-strings-issue :? :1
+-- :doc selects existing issue records having any string field matching `:pattern` by substring match
+SELECT * FROM issues
+WHERE
+url LIKE '%:pattern%'
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name search-strings-role :? :1
+-- :doc selects existing role records having any string field matching `:pattern` by substring match
+SELECT * FROM roles
+WHERE
+name LIKE '%:pattern%'
+ORDER BY roles.name,
+ roles.id
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
+
+-- :name search-strings-team :? :1
+-- :doc selects existing team records having any string field matching `:pattern` by substring match
+SELECT * FROM teams
+WHERE
+name LIKE '%:pattern%'
+ORDER BY teams.name,
+ teams.id
+--~ (if (:offset params) "OFFSET :offset ")
+--~ (if (:limit params) "LIMIT :limit" "LIMIT 100")
diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql
index 4191f67..e2170b8 100644
--- a/resources/sql/queries.sql
+++ b/resources/sql/queries.sql
@@ -1,21 +1,344 @@
--- :name create-user! :! :n
--- :doc creates a new user record
-INSERT INTO users
-(id, first_name, last_name, email, pass)
-VALUES (:id, :first_name, :last_name, :email, :pass)
+------------------------------------------------------------------------------;
+----
+---- youyesyet.routes.authenticated: routes and pages for authenticated users.
+----
+---- This program is free software; you can redistribute it and/or
+---- modify it under the terms of the GNU General Public License
+---- as published by the Free Software Foundation; either version 2
+---- of the License, or (at your option) any later version.
+----
+---- This program is distributed in the hope that it will be useful,
+---- but WITHOUT ANY WARRANTY; without even the implied warranty of
+---- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+---- GNU General Public License for more details.
+----
+---- You should have received a copy of the GNU General Public License
+---- along with this program; if not, write to the Free Software
+---- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
+---- USA.
+----
+---- Copyright (C) 2016 Simon Brooke for Radical Independence Campaign
+----
+------------------------------------------------------------------------------;
--- :name update-user! :! :n
--- :doc update an existing user record
-UPDATE users
-SET first_name = :first_name, last_name = :last_name, email = :email
+-- This file gets slurped in and converted into simple functions by the line
+-- in youyesyet.db.core.clj:
+-- (conman/bind-connection *db* "sql/queries.sql")
+-- the functions then appeare in the youyesyet.db.core namespace.
+
+-- :name create-address! :! :n
+-- :doc creates a new address record
+INSERT INTO addresses
+(address, postcode, district_id, latitude, longitude)
+VALUES (:address, :postcode, :district, :latitude, :longitude)
+RETURNING id
+
+-- :name update-address! :! :n
+-- :doc update an existing address record
+UPDATE addresses
+SET address = :address, postcode = :postcode, latitude = :latitude, longitude = :longitude
WHERE id = :id
--- :name get-user :? :1
--- :doc retrieve a user given the id.
-SELECT * FROM users
+-- :name get-address :? :1
+-- :doc retrieve a address given the id.
+SELECT * FROM addresses
WHERE id = :id
--- :name delete-user! :! :n
--- :doc delete a user given the id
-DELETE FROM users
+-- :name get-addresses-by-postcode
+
+-- :name delete-address! :! :n
+-- :doc delete a address given the id
+DELETE FROM addresses
WHERE id = :id
+
+
+-- :name create-authority! :! :n
+-- :doc creates a new authority record
+INSERT INTO authorities
+(id)
+VALUES (:id)
+RETURNING id
+
+-- :name update-authority! :! :n
+-- :doc update an existing authority record
+UPDATE authorities
+SET id = :id
+WHERE id = :id
+
+-- :name get-authority :? :1
+-- :doc retrieve a authority given the id.
+SELECT * FROM authorities
+WHERE id = :id
+
+-- :name get-authorities :? :0
+-- :doc retrieve all authorities
+SELECT id FROM authorities
+
+-- :name delete-authority! :! :n
+-- :doc delete a authority given the id
+DELETE FROM authorities
+WHERE id = :id
+
+
+-- :name create-canvasser! :! :n
+-- :doc creates a new canvasser record
+INSERT INTO canvassers
+(username, fullname, elector_id, dwelling_id, phone, email, authority_id, authorised)
+VALUES (:username, :fullname, :elector_id, :dwelling_id, :phone, :email, :authority_id, :authorised)
+RETURNING id
+
+-- :name update-canvasser! :! :n
+-- :doc update an existing canvasser record
+UPDATE canvassers
+SET username = :username, fullname = :fullname, elector_id = :elector_id, dwelling_id = :dwelling_id, phone = :phone, email = :email, authority_id = :authority_id, authorised = :authorised
+WHERE id = :id
+
+-- :name get-canvasser :? :1
+-- :doc retrieve a canvasser given the id.
+SELECT * FROM canvassers
+WHERE id = :id
+
+-- :name get-canvasser-by-username :? :1
+-- :doc rerieve a canvasser given the username.
+SELECT * FROM canvassers
+WHERE username = :username
+
+-- :name get-canvasser-by-email :? :1
+-- :doc rerieve a canvasser given the email address.
+SELECT * FROM canvassers
+WHERE email = :email
+
+-- :name delete-canvasser! :! :n
+-- :doc delete a canvasser given the id
+DELETE FROM canvassers
+WHERE id = :id
+
+
+-- :name create-district! :! :n
+-- :doc creates a new district record
+INSERT INTO districts
+(id, name)
+VALUES (:id, :name)
+RETURNING id
+
+-- :name update-district! :! :n
+-- :doc update an existing district record
+UPDATE districts
+SET name = :name
+WHERE id = :id
+
+-- :name get-district :? :1
+-- :doc retrieve a district given the id.
+SELECT * FROM districts
+WHERE id = :id
+
+-- :name delete-district! :! :n
+-- :doc delete a district given the id
+DELETE FROM districts
+WHERE id = :id
+
+
+-- :name get-dwelling :? :1
+-- :doc retrieve a dwelling given the id.
+SELECT * FROM dwellings
+WHERE id = :id
+
+-- :name delete-dwelling! :! :n
+-- :doc delete a dwelling given the id
+DELETE FROM dwellings
+WHERE id = :id
+
+-- :name create-dwelling! :! :n
+-- :doc creates a new dwelling record
+INSERT INTO dwellings
+(id, address_id, sub_address)
+VALUES (:id, :address_id, :sub_address)
+RETURNING id
+
+-- :name update-dwelling! :! :n
+-- :doc update an existing dwelling record
+UPDATE dwellings
+SET address_id = :address_id,
+ sub_address = :sub_address
+WHERE id = :id
+
+-- :name get-dwelling :? :1
+-- :doc retrieve a dwelling given the id.
+SELECT * FROM dwellings
+WHERE id = :id
+
+-- :name delete-dwelling! :! :n
+-- :doc delete a dwelling given the id
+DELETE FROM dwellings
+WHERE id = :id
+
+
+-- :name create-elector! :! :n
+-- :doc creates a new elector record
+INSERT INTO electors
+(name, dwelling_id, phone, email)
+VALUES (:name, :dwelling_id, :phone, :email)
+RETURNING id
+
+-- :name update-elector! :! :n
+-- :doc update an existing elector record
+UPDATE electors
+SET name = :name, dwelling_id = :dwelling_id, phone = :phone, email = :email
+WHERE id = :id
+
+-- :name get-elector :? :1
+-- :doc retrieve a elector given the id.
+SELECT * FROM electors
+WHERE id = :id
+
+-- :name delete-elector! :! :n
+-- :doc delete a elector given the id
+DELETE FROM electors
+WHERE id = :id
+
+
+-- :name create-followupaction! :! :n
+-- :doc creates a new followupaction record
+INSERT INTO followupactions
+(request_id, actor, date, notes, closed)
+VALUES (:request_id, :actor, :date, :notes, :closed)
+RETURNING id
+
+-- We don't update followup actions. They're permanent record.
+
+-- :name get-followupaction :? :1
+-- :doc retrieve a followupaction given the id.
+SELECT * FROM followupactions
+WHERE id = :id
+
+-- We don't delete followup actions. They're permanent record.
+
+
+-- followup methods are reference data, do not need to be programmatically maintained.
+
+
+-- :name create-followuprequest! :! :n
+-- :doc creates a new followupaction record
+INSERT INTO followuprequests
+(elector_id, visit_id, issue_id, method_id)
+VALUES (:elector_id, :visit_id, :issue_id, :method_id)
+RETURNING id
+
+-- We don't update followup requests. They're permanent record.
+
+-- :name get-followuprequest :? :1
+-- :doc retrieve a followupaction given the id.
+SELECT * FROM followuprequests
+WHERE id = :id
+
+-- We don't delete followup requests. They're permanent record.
+
+
+-- :name create-issueexpertise! :! :n
+-- :doc creates a new issueexpertise record
+INSERT INTO issueexpertise
+(canvasser_id, issue_id, method_id)
+VALUES (:canvasser_id, :issue_id, :method_id)
+-- issueexertise is a link table, doesn't have an id field.
+
+-- :name update-issueexpertise! :! :n
+-- :doc update an existing issueexpertise record
+UPDATE issueexpertise
+SET canvasser_id = :canvasser_id, issue_id = :issue_id, method_id = :method_id
+WHERE id = :id
+
+-- :name get-issueexpertise :? :1
+-- :doc retrieve a issueexpertise given the canvasser_id -
+-- getting it by its own id is unlikely to be interesting or useful.
+SELECT * FROM issueexpertise
+WHERE canvasser_id = :canvasser_id
+
+-- :name delete-issueexpertise! :! :n
+-- :doc delete a issueexpertise given the id
+DELETE FROM issueexpertise
+WHERE id = :id
+
+
+-- :name create-issue! :! :n
+-- :doc creates a new issue record
+INSERT INTO issues
+(id, url, content, current)
+VALUES (:id, :url, :content, :current)
+RETURNING id
+
+
+-- :name update-issue! :! :n
+-- :doc update an existing issue record
+UPDATE issues
+SET url = :url, content = :content, current = :current
+WHERE id = :id
+
+-- :name get-issue :? :1
+-- :doc retrieve a issue given the id -
+SELECT * FROM issues
+WHERE id = :id
+
+-- :name delete-issue! :! :n
+-- :doc delete a issue given the id
+DELETE FROM issues
+WHERE id = :id
+
+
+-- options is virtually reference data; it's not urgent to create a programmatic means of editing
+
+-- :name create-visit! :! :n
+-- :doc creates a new visit record
+INSERT INTO visits
+(dwelling_id, canvasser_id)
+VALUES (:dwelling_id, :canvasser_id)
+RETURNING id
+
+-- visits is audit data; we don't update it.
+
+-- :name get-visit :? :1
+-- :doc retrieve a visit given the id.
+SELECT * FROM visits
+WHERE id = :id
+
+-- visits is audit data; we don't delete it.
+
+
+-- views are select only
+
+-- :name get-roles-by-canvasser :? :*
+-- :doc Get the role names for the canvasser with the specified id
+select name from roles_by_canvasser
+ where canvasser = :canvasser
+
+-- :name get-teams-by-canvasser :? :*
+-- :doc Get details of the teams which the canvasser with the specified id is member of.
+select * from teams_by_canvasser
+ where canvasser = :canvasser_id
+
+-- :name get-canvassers-by-team :? :*
+-- :doc Get details of all canvassers who are members of the team with the specified id
+select * from canvassers_by_team
+ where team = :team_id
+
+-- :name get-canvassers-by-team :? :*
+-- :doc Get details of all authorised canvassers who are members of this team.
+select * from canvassers_by_introducer
+ where introducer = :introducer_id
+
+-- :name get-canvassers-by-search :? :*
+-- :doc Get details of all authorised canvassers whose details match this search string.
+select * from canvassers
+ where name like '%' || :search || '%'
+ or username like '%' || :search || '%'
+ or email like '%' || :search || '%'
+
+-- :name get-teams_by_organiser :? :*
+-- :doc Get details of all the teams organised by the canvasser with the specified id
+select * from teams_by_organiser
+ where organiser = :organiser_id
+
+-- :name get-organisers-by-team :? :*
+-- :doc Get details of all organisers of the team with the specified id
+select * from organisers_by_team
+ where team = :team_id
+
diff --git a/resources/templates/app.html b/resources/templates/app.html
index 5214806..024aa39 100644
--- a/resources/templates/app.html
+++ b/resources/templates/app.html
@@ -1,16 +1,5 @@
-
-
-
-
-
-
-
-
-
- You Yes Yet?
-
-
-
+{% extends "base-authenticated.html" %}
+{% block whole-page %}
@@ -33,21 +22,16 @@
You must enable JavaScript to use the You Yes Yet app.