diff --git a/doc/specification/database.md b/doc/specification/database.md new file mode 100644 index 0000000..2656621 --- /dev/null +++ b/doc/specification/database.md @@ -0,0 +1,218 @@ +# 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 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 + ); + + +## 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, + address_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..7068dd2 100644 --- a/doc/specification/entity-relationship-diagram.svg +++ b/doc/specification/entity-relationship-diagram.svg @@ -25,9 +25,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="1.979899" - inkscape:cx="833.70674" - inkscape:cy="324.89697" + inkscape:zoom="2.8" + inkscape:cx="764.16287" + inkscape:cy="256.90499" inkscape:document-units="px" inkscape:current-layer="layer1" showgrid="true" @@ -58,12 +58,13 @@ id="layer1" transform="translate(0,-308.26772)"> + x="8.484766" + y="312.36221" + sodipodi:insensitive="true" /> Addresss + sodipodi:role="line">Address @@ -613,14 +614,14 @@ y="394.48404" style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;font-family:Arial;-inkscape-font-specification:Arial">Version: 0.2Version: 0.3Date: 20170315Date: 20170401 + + FollowupMethod + + + + 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..e69de29 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..6ca3028 --- /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 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/sql/queries.sql b/resources/sql/queries.sql index 4191f67..ab15d8e 100644 --- a/resources/sql/queries.sql +++ b/resources/sql/queries.sql @@ -19,3 +19,4 @@ WHERE id = :id -- :doc delete a user given the id DELETE FROM users WHERE id = :id + diff --git a/resources/templates/base-authenticated.html b/resources/templates/base-authenticated.html new file mode 100644 index 0000000..79c1558 --- /dev/null +++ b/resources/templates/base-authenticated.html @@ -0,0 +1,74 @@ + + + + + + + + + {% block title %}{% endblock %}{{title}} + + +
+
+ You are logged in as {{username}} +
+ + +

+ {% block title %} + {% endblock %} + {{title}} +

+
+ +
+ +
+ {{content}} +
+ + +
+ + + + + + + + diff --git a/resources/templates/base-unauthenticated.html b/resources/templates/base-unauthenticated.html new file mode 100644 index 0000000..ba59e7b --- /dev/null +++ b/resources/templates/base-unauthenticated.html @@ -0,0 +1,58 @@ + + + + + + + + + {% block title %}{% endblock %}{{title}} + + +
+ + +

+ {{title}} +

+
+ +
+ +
+ {% block content %} + {% endblock %} +
+
+ + + + diff --git a/resources/templates/roles.html b/resources/templates/roles.html new file mode 100644 index 0000000..1c61866 --- /dev/null +++ b/resources/templates/roles.html @@ -0,0 +1,13 @@ +{% extends "base-authenticated.html" %} +{% block title %} +{% endblock %} +{% block big-links %} + + {% for role in roles %} + + {% endfor %} +{% endblock %} diff --git a/src/clj/youyesyet/db/core.clj b/src/clj/youyesyet/db/core.clj index 536cc3b..a0eec57 100644 --- a/src/clj/youyesyet/db/core.clj +++ b/src/clj/youyesyet/db/core.clj @@ -15,9 +15,13 @@ Timestamp PreparedStatement])) +;; TODO: I am CERTANLY misunderstanding something here. We ought to be getting +;; the database connection string and credentials fomr profiles.clj +;; (def ^:dynamic *db* {:name "java:comp/env/jdbc/EmployeeDB"}) (defstate ^:dynamic *db* - :start (conman/connect! {:jdbc-url (env :database-url)}) - :stop (conman/disconnect! *db*)) + :start (conman/connect! {:jdbc-url (env :database-url) + :driver-class-name "org.postgresql.Driver"}) + :stop (conman/disconnect! *db*)) (conman/bind-connection *db* "sql/queries.sql") diff --git a/src/clj/youyesyet/handler.clj b/src/clj/youyesyet/handler.clj index f898fe5..ad5462d 100644 --- a/src/clj/youyesyet/handler.clj +++ b/src/clj/youyesyet/handler.clj @@ -10,6 +10,29 @@ [clojure.tools.logging :as log] [youyesyet.config :refer [env]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; youyesyet.handler: handlers for starting and stopping the webapp. +;;;; +;;;; 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 +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (mount/defstate init-app :start ((or (:init defaults) identity)) :stop ((or (:stop defaults) identity))) diff --git a/src/clj/youyesyet/routes/authenticated.clj b/src/clj/youyesyet/routes/authenticated.clj new file mode 100644 index 0000000..b5c2644 --- /dev/null +++ b/src/clj/youyesyet/routes/authenticated.clj @@ -0,0 +1,35 @@ +(ns youyesyet.routes.authenticated + (:require [clojure.walk :refer [keywordize-keys]] + [noir.response :as nresponse] + [noir.util.route :as route] + [youyesyet.layout :as layout] + [youyesyet.db.core :as db-core] + [compojure.core :refer [defroutes GET POST]] + [ring.util.http-response :as response] + [clojure.java.io :as io])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; 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 +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; (defn roles-page [request] +;; (if diff --git a/src/clj/youyesyet/routes/home.clj b/src/clj/youyesyet/routes/home.clj index 7fbf9f7..90f2f12 100644 --- a/src/clj/youyesyet/routes/home.clj +++ b/src/clj/youyesyet/routes/home.clj @@ -1,15 +1,81 @@ (ns youyesyet.routes.home - (:require [youyesyet.layout :as layout] + (:require [clojure.walk :refer [keywordize-keys]] + [noir.response :as nresponse] + [noir.util.route :as route] + [youyesyet.layout :as layout] [youyesyet.db.core :as db-core] - [compojure.core :refer [defroutes GET]] + [compojure.core :refer [defroutes GET POST]] [ring.util.http-response :as response] [clojure.java.io :as io])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; youyesyet.routes.home: routes and pages for unauthenticated 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 +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn app-page [] + (layout/render "app.html")) + +(defn about-page [] + (layout/render "about.html")) + +(defn call-me-page [request] + (if + request + (do + ;; do something to store it in the database + (layout/render "call-me-accepted.html" (:params request))) + (layout/render "call-me.html" + {:title "Please call me!" + ;; TODO: Issues need to be fetched from the database + :concerns nil}))) + (defn home-page [] - (layout/render "home.html")) + (layout/render "home.html" {:title "You Yes Yet?"})) + +(defn login-page + "This is very temporary. We're going to do authentication by oauth." + [request] + (let [params (keywordize-keys (:form-params request)) + session (:session request) + username (:username params) + password (:password params) + redirect-to (or (:redirect-to params) "app")] + (if + (and (= username "test") (= password "test")) + (do + (assoc (response/found redirect-to) :session (assoc session :user username))) + (layout/render "login.html" {:title "Please log in" :redirect-to redirect-to})))) + (defroutes home-routes (GET "/" [] (home-page)) - (GET "/docs" [] (-> (response/ok (-> "docs/docs.md" io/resource slurp)) - (response/header "Content-Type" "text/plain; charset=utf-8")))) - + (GET "/home" [] (home-page)) + (GET "/about" [] (about-page)) + (GET "/app" [] (route/restricted (app-page))) + (GET "/call-me" [] (call-me-page nil)) + (POST "/call-me" request (call-me-page request)) + (GET "/auth" request (login-page request)) + (POST "/auth" request (login-page request)) + (GET "/notyet" [] (layout/render "notyet.html" + {:title "Can we persuade you?"})) + (GET "/supporter" [] (layout/render "supporter.html" + {:title "Have you signed up as a canvasser yet?"}))) diff --git a/src/cljc/youyesyet/validation.cljc b/src/cljc/youyesyet/validation.cljc index fd01d8f..7c293dd 100644 --- a/src/cljc/youyesyet/validation.cljc +++ b/src/cljc/youyesyet/validation.cljc @@ -1,3 +1,26 @@ (ns youyesyet.validation (:require [bouncer.core :as b] [bouncer.validators :as v])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; youyesyet.validation: +;;;; +;;;; 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 +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/src/cljs/youyesyet/ajax.cljs b/src/cljs/youyesyet/ajax.cljs index 07cf00d..ef3a3cb 100644 --- a/src/cljs/youyesyet/ajax.cljs +++ b/src/cljs/youyesyet/ajax.cljs @@ -1,6 +1,30 @@ (ns youyesyet.ajax (:require [ajax.core :as ajax])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; youyesyet.ajax: transciever for ajax packets. +;;;; +;;;; 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 +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + (defn local-uri? [{:keys [uri]}] (not (re-find #"^\w+?://" uri)))