Merge branch 'feature/5' into feature/41

This commit is contained in:
simon 2017-07-21 12:40:57 +01:00
commit 88c5c74c52
51 changed files with 1187 additions and 162 deletions

View file

@ -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

View file

@ -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';

View file

@ -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');

View file

@ -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;

View file

@ -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;

View file

@ -34,7 +34,7 @@
CREATE TABLE IF NOT EXISTS dwellings (
id serial NOT NULL primary key,
address_id integer NOT NULL,
address_id integer NOT NULL references addresses(id),
sub_address varchar(16)
);
--;;

View file

@ -46,6 +46,7 @@
#nav-menu {
margin: 0;
padding: 0;
width: 100%;
}
#nav menu li {

View file

@ -1,21 +1,309 @@
-- :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, address_id, phone, email, authority_id, authorised)
VALUES (:username, :fullname, :elector_id, :address_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, address_id = :address_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 create-elector! :! :n
-- :doc creates a new elector record
INSERT INTO electors
(name, address_id, phone, email)
VALUES (:name, :address_id, :phone, :email)
RETURNING id
-- :name update-elector! :! :n
-- :doc update an existing elector record
UPDATE electors
SET name = :name, address_id = :address_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
(address_id, canvasser_id)
VALUES (:address_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

View file

@ -1,16 +1,5 @@
<!DOCTYPE html>
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/yyy-common.css" />
<link rel="stylesheet" type="text/css" href="css/yyy-app.css" />
<link rel="stylesheet" type="text/css" href="css/spinner.css" />
<link href="https://fonts.googleapis.com/css?family=Archivo+Black|Archivo+Narrow" rel="stylesheet"/>
<title>You Yes Yet?</title>
</head>
<body>
{% extends "base-authenticated.html" %}
{% block whole-page %}
<div id="app">
<div class="splash-screen">
<div class="sk-fading-circle">
@ -33,21 +22,16 @@
You must enable JavaScript to use the You Yes Yet app.
</p>
</div>
<!-- scripts and styles -->
<!-- ATTENTION \/ -->
<!-- Leaflet -->
<link rel="stylesheet" href="vendor/leaflet/dist/leaflet.css" />
<script src="vendor/leaflet/dist/leaflet.js"></script>
<!-- ATTENTION /\ -->
<script type="text/javascript">
var context = "{{servlet-context}}";
var csrfToken = "{{csrf-token}}";
</script>
{% script "/js/app.js" %}
</body>
</html>
{% endblock %}
{% block extra-script %}
<!-- scripts and styles -->
<!-- ATTENTION \/ -->
<!-- Leaflet -->
<link rel="stylesheet" href="vendor/leaflet/dist/leaflet.css" />
<script src="vendor/leaflet/dist/leaflet.js"></script>
<!-- ATTENTION /\ -->
{% script "/js/app.js" %}
{% endblock %}

View file

@ -3,12 +3,14 @@
<head>
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" type="text/css" href="css/yyy-static.css" />
<link rel="stylesheet" type="text/css" href="css/yyy-common.css" />
<link rel="stylesheet" type="text/css" href="css/yyy-site.css" />
<link rel="stylesheet" type="text/css" href="css/spinner.css" />
<link href="https://fonts.googleapis.com/css?family=Archivo+Black|Archivo+Narrow" rel="stylesheet"/>
<title>{{title}}</title>
</head>
<body>
{% block whole-page %}
<header>
<div id="nav">
<img id="nav-icon" src="img/threelines.png" alt="Menu"/>
@ -16,8 +18,12 @@
<li class=""><a href="index.html">Home</a></li>
<li class=""><a href="library.html">Library</a></li>
<li class=""><a href="register.html">Register</a></li>
<li class=""><a href="login.html">Login</a></li>
<li class="">{% if user %}<a href="logout.html">Logout</a>
{% else %}<a href="login.html">Login</a>{% endif %}</li>
<li class=""><a href="about.html">About</a></li>
{% if user %}
<li id="user"><a href="profile">Logged in as {{user.username}}</a></li>
{% endif %}
</menu>
</div>
@ -28,12 +34,40 @@
<div id="main-container" class="container">
<div id="big-links">
{{big-links}}
</div>
{% block big-links %}
{% endblock %}
</div>
<div if="#content">
{{content}}
{% block content %}
{% endblock %}
</div>
<div id="back-link-container">
<a href="javascript:history.back()" id="back-link">Back</a>
</div>
</div>
<footer>
<div id="credits">
<div>
<img src="img/credits/ric-logo.png" width="24" height="24"/>
A project of the
<a href="https://radical.scot/">Radical Independence Campaign</a> ||
Version {{version}}
</div>
<div>
<img height="16" width="16" alt="Clojure" src="img/credits/luminus-logo.png"/>Built with <a href="http://www.luminusweb.net/">LuminusWeb</a> ||
<img height="16" width="16" alt="Clojure" src="img/credits/clojure-icon.gif"/> Powered by <a href="http://clojure.org">Clojure</a> ||
<img height="16" width="16" alt="GitHub" src="img/credits/github-logo-transparent.png"/>Find me/fork me on <a href="https://github.com/simon-brooke/smeagol">Github</a> ||
<img height="16" width="16" alt="Free Software Foundation" src="img/credits/gnu.small.png"/>Licensed under the <a href="http://www.gnu.org/licenses/gpl-2.0.html">GNU General Public License version 2.0</a>
</div>
</div>
</footer>
{% endblock %}
<script type="text/javascript">
var context = "{{servlet-context}}";
var csrfToken = "{{csrf-token}}";
</script>
{% block extra-script %}
{% endblock %}
</body>
</html>

View file

@ -6,7 +6,7 @@
<link rel="stylesheet" type="text/css" href="css/yyy-common.css" />
<link rel="stylesheet" type="text/css" href="css/yyy-site.css" />
<link href="https://fonts.googleapis.com/css?family=Archivo+Black|Archivo+Narrow" rel="stylesheet"/>
<title>{{title}}</title>
<title>{% block title %}{% endblock %}{{title}}</title>
</head>
<body>
<header>

View file

@ -0,0 +1,60 @@
{% extends "base-authenticated.html" %}
{% block title %}
{% endblock %}
{% block content %}
<form action="edit-canvasser" method="post">
{% if canvasser %}
<input type="hidden" name="id" id="id" value="{{canvasser.id}}"/>
{% endif %}
<p class="widget">
<label for="fullname">Full name</label>
<input type="text" name="fullname" id="fullname" value="{{canvasser.fullname}}"/>
</p>
<p class="widget">
(TODO: Not absolutely sure what I'm going to do for an elector id widget yet.)
</p>
<p class="widget">
<label for="address">Address</label>
{% if address.id %}
<!-- if we already have an address, just show it with a link to edit it -->
<span class="pseudo-widget" id="address">
{{address.address}}
</span>
{% else %}
(TODO: Some sort of address lookup widget goes here.)
{% endif %}
</p>
<p class="widget">
<label for="phone">Phone number</label>
<input type="tel" name="phone" id="phone" value="{{canvasser.phone}}"/>
</p>
<p class="widget">
<label for="email">Email address</label>
<input type="email" name="email" id="email" value="{{canvasser.email}}"/>
</p>
<p class="widget">
<label for="authority_id">Authorised by</label>
<select name="authority_id" id="authority_id">
{% for authority in authorities %}
<option value="{{authority.id}}"
{% ifequal authority.id canvasser.authority_id %}selected {% endifequal %}>
{{authority.id}}
</option>
</select>
</p>
</p>
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
</form>
{% endblock %}

View file

@ -1,9 +1,4 @@
{% extends "base-unauthenticated.html" %}
{% block big-links %}
<div id="back-link-container">
<a href="javascript:history.back()" id="back-link">Back</a>
</div>
{% endblock %}
{% block content %}
<p>
We're not going to do login in the long term; we're going to use oauth.

View file

@ -0,0 +1,16 @@
{% extends "base-authenticated.html" %}
{% block title %}
{{ user }}
{% endblock %}
{% block big-links %}
<div class="big-link-container">
<a href="app" class="big-link" id="big-link">Canvasser</a>
</div>
{% for role in roles %}
<div class="big-link-container">
<a href="{{role.name|lower}}" class="big-link" id="big-link">{{role.name}}</a>
</div>
{% endfor %}
{% endblock %}
{% block content %}
{% endblock %}