From 6b44b7033095271e91ac2f3f94ef98aa84ebd809 Mon Sep 17 00:00:00 2001 From: Simon Brooke Date: Tue, 3 Jul 2018 10:04:59 +0100 Subject: [PATCH] #31, #44, #45: GDPR workflow complete; started on locality data --- project.clj | 2 +- resources/sql/locality-trigger.sql | 35 ++ resources/sql/queries.auto.sql | 58 ++-- resources/sql/queries.sql | 325 +----------------- src/clj/youyesyet/db/core.clj | 2 +- src/clj/youyesyet/handler.clj | 3 + src/clj/youyesyet/routes/rest.clj | 28 +- src/cljc/youyesyet/locality.cljc | 54 +++ src/cljc/youyesyet/utils.cljc | 35 ++ src/cljs/youyesyet/canvasser_app/core.cljs | 12 + .../youyesyet/canvasser_app/handlers.cljs | 10 +- .../youyesyet/canvasser_app/ui_utils.cljs | 1 + .../canvasser_app/views/elector.cljs | 21 +- .../canvasser_app/views/followup.cljs | 8 +- .../youyesyet/canvasser_app/views/gdpr.cljs | 13 +- 15 files changed, 223 insertions(+), 384 deletions(-) create mode 100644 resources/sql/locality-trigger.sql create mode 100644 src/cljc/youyesyet/locality.cljc create mode 100644 src/cljc/youyesyet/utils.cljc diff --git a/project.clj b/project.clj index 9feddee..8f52343 100644 --- a/project.clj +++ b/project.clj @@ -77,7 +77,7 @@ :npm {:dependencies [[datatables.net "1.10.19"] [datatables.net-dt "1.10.19"] [jquery "3.3.1"] - [leaflet "0.7.3"] ;; old version works, new ["1.3.1"] doesn't + [leaflet "0.7.3"] ;; old version works, new ["1.3.1"] doesn't [signature_pad "2.3.2"]] :root "resources/public/js/lib"} diff --git a/resources/sql/locality-trigger.sql b/resources/sql/locality-trigger.sql new file mode 100644 index 0000000..cbda59c --- /dev/null +++ b/resources/sql/locality-trigger.sql @@ -0,0 +1,35 @@ +------------------------------------------------------------------------------; +---- +---- locality-trigger.sql: compute localities for 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) 2016 Simon Brooke for Radical Independence Campaign +---- +------------------------------------------------------------------------------; + +---- See also: src/cljc/locality.cljc + +CREATE FUNCTION compute_locality() RETURNS trigger AS $compute_locality$ + BEGIN + NEW.locality = (10000 * floor (NEW.latitude * 1000)) - + (NEW.longitude * 1000); + RETURN NEW; + END; +$compute_locality$ LANGUAGE plpgsql; + +CREATE TRIGGER compute_locality BEFORE INSERT OR UPDATE ON addresses + FOR EACH ROW EXECUTE PROCEDURE compute_locality(); diff --git a/resources/sql/queries.auto.sql b/resources/sql/queries.auto.sql index 27bdde1..5d024dc 100644 --- a/resources/sql/queries.auto.sql +++ b/resources/sql/queries.auto.sql @@ -1,10 +1,10 @@ ------------------------------------------------------------------------ --- File queries.sql --- --- autogenerated by adl.to-hugsql-queries at 2018-07-01T22:15:28.111Z --- --- See [Application Description --- Language](https://github.com/simon-brooke/adl). +-- File queries.sql +-- +-- autogenerated by adl.to-hugsql-queries at 2018-07-01T22:15:28.111Z +-- +-- See [Application Description +-- Language](https://github.com/simon-brooke/adl). ------------------------------------------------------------------------ -- :name create-address! :! :n @@ -405,7 +405,7 @@ ORDER BY lv_addresses.address, -- :name list-addresses-by-district :? :* -- :doc lists all existing address records related to a given district -SELECT * +SELECT * FROM lv_addresses, addresses WHERE lv_addresses.id = addresses.id AND addresses.district_id = :id @@ -433,7 +433,7 @@ ORDER BY lv_canvassers.username, -- :name list-canvassers-by-address :? :* -- :doc lists all existing canvasser records related to a given address -SELECT * +SELECT * FROM lv_canvassers, canvassers WHERE lv_canvassers.id = canvassers.id AND canvassers.address_id = :id @@ -446,7 +446,7 @@ ORDER BY lv_canvassers.username, -- :name list-canvassers-by-authority :? :* -- :doc lists all existing canvasser records related to a given authority -SELECT * +SELECT * FROM lv_canvassers, canvassers WHERE lv_canvassers.id = canvassers.id AND canvassers.authority_id = :id @@ -459,7 +459,7 @@ ORDER BY lv_canvassers.username, -- :name list-canvassers-by-elector :? :* -- :doc lists all existing canvasser records related to a given elector -SELECT * +SELECT * FROM lv_canvassers, canvassers WHERE lv_canvassers.id = canvassers.id AND canvassers.elector_id = :id @@ -472,7 +472,7 @@ ORDER BY lv_canvassers.username, -- :name list-canvassers-by-role :? :* -- :doc links all existing canvasser records related to a given role -SELECT * +SELECT * FROM canvassers, ln_canvassers_roles WHERE canvassers.id = ln_canvassers_roles.canvasser_id AND ln_canvassers_roles.role_id = :id @@ -502,7 +502,7 @@ ORDER BY lv_dwellings.address_id, -- :name list-dwellings-by-address :? :* -- :doc lists all existing dwelling records related to a given address -SELECT * +SELECT * FROM lv_dwellings, dwellings WHERE lv_dwellings.id = dwellings.id AND dwellings.address_id = :id @@ -523,7 +523,7 @@ ORDER BY lv_electors.name, -- :name list-electors-by-dwelling :? :* -- :doc lists all existing elector records related to a given dwelling -SELECT * +SELECT * FROM lv_electors, electors WHERE lv_electors.id = electors.id AND electors.dwelling_id = :id @@ -535,7 +535,7 @@ ORDER BY lv_electors.name, -- :name list-electors-by-gender :? :* -- :doc lists all existing elector records related to a given gender -SELECT * +SELECT * FROM lv_electors, electors WHERE lv_electors.id = electors.id AND electors.gender = :id @@ -556,7 +556,7 @@ ORDER BY lv_followupactions.date, -- :name list-followupactions-by-canvasser :? :* -- :doc lists all existing followupaction records related to a given canvasser -SELECT * +SELECT * FROM lv_followupactions, followupactions WHERE lv_followupactions.id = followupactions.id AND followupactions.actor = :id @@ -566,7 +566,7 @@ ORDER BY lv_followupactions.date, -- :name list-followupactions-by-followuprequest :? :* -- :doc lists all existing followupaction records related to a given followuprequest -SELECT * +SELECT * FROM lv_followupactions, followupactions WHERE lv_followupactions.id = followupactions.id AND followupactions.request_id = :id @@ -592,7 +592,7 @@ ORDER BY lv_followuprequests.elector_id, -- :name list-followuprequests-by-elector :? :* -- :doc lists all existing followuprequest records related to a given elector -SELECT * +SELECT * FROM lv_followuprequests, followuprequests WHERE lv_followuprequests.id = followuprequests.id AND followuprequests.elector_id = :id @@ -603,7 +603,7 @@ ORDER BY lv_followuprequests.elector_id, -- :name list-followuprequests-by-followupmethod :? :* -- :doc lists all existing followuprequest records related to a given followupmethod -SELECT * +SELECT * FROM lv_followuprequests, followuprequests WHERE lv_followuprequests.id = followuprequests.id AND followuprequests.method_id = :id @@ -614,7 +614,7 @@ ORDER BY lv_followuprequests.elector_id, -- :name list-followuprequests-by-issue :? :* -- :doc lists all existing followuprequest records related to a given issue -SELECT * +SELECT * FROM lv_followuprequests, followuprequests WHERE lv_followuprequests.id = followuprequests.id AND followuprequests.issue_id = :id @@ -625,7 +625,7 @@ ORDER BY lv_followuprequests.elector_id, -- :name list-followuprequests-by-visit :? :* -- :doc lists all existing followuprequest records related to a given visit -SELECT * +SELECT * FROM lv_followuprequests, followuprequests WHERE lv_followuprequests.id = followuprequests.id AND followuprequests.visit_id = :id @@ -648,21 +648,21 @@ SELECT DISTINCT * FROM lv_intentions -- :name list-intentions-by-elector :? :* -- :doc lists all existing intention records related to a given elector -SELECT * +SELECT * FROM lv_intentions, intentions WHERE lv_intentions.Id = intentions.Id AND intentions.elector_id = :id -- :name list-intentions-by-option :? :* -- :doc lists all existing intention records related to a given option -SELECT * +SELECT * FROM lv_intentions, intentions WHERE lv_intentions.Id = intentions.Id AND intentions.option_id = :id -- :name list-intentions-by-visit :? :* -- :doc lists all existing intention records related to a given visit -SELECT * +SELECT * FROM lv_intentions, intentions WHERE lv_intentions.Id = intentions.Id AND intentions.visit_id = :id @@ -689,7 +689,7 @@ ORDER BY lv_roles.name, -- :name list-roles-by-canvasser :? :* -- :doc links all existing role records related to a given canvasser -SELECT * +SELECT * FROM roles, ln_canvassers_roles WHERE roles.id = ln_canvassers_roles.role_id AND ln_canvassers_roles.canvasser_id = :id @@ -706,7 +706,7 @@ ORDER BY lv_teams.name, -- :name list-teams-by-canvasser :? :* -- :doc links all existing team records related to a given canvasser -SELECT * +SELECT * FROM teams, ln_canvassers_teams WHERE teams.id = ln_canvassers_teams.team_id AND ln_canvassers_teams.canvasser_id = :id @@ -715,7 +715,7 @@ ORDER BY teams.name, -- :name list-teams-by-district :? :* -- :doc lists all existing team records related to a given district -SELECT * +SELECT * FROM lv_teams, teams WHERE lv_teams.id = teams.id AND teams.district_id = :id @@ -733,7 +733,7 @@ ORDER BY lv_visits.address_id, -- :name list-visits-by-address :? :* -- :doc lists all existing visit records related to a given address -SELECT * +SELECT * FROM lv_visits, visits WHERE lv_visits.id = visits.id AND visits.address_id = :id @@ -743,7 +743,7 @@ ORDER BY lv_visits.address_id, -- :name list-visits-by-canvasser :? :* -- :doc lists all existing visit records related to a given canvasser -SELECT * +SELECT * FROM lv_visits, visits WHERE lv_visits.id = visits.id AND visits.canvasser_id = :id @@ -1079,4 +1079,4 @@ UPDATE visits SET address_id = :address_id, canvasser_id = :canvasser_id, date = :date -WHERE visits.id = :id \ No newline at end of file +WHERE visits.id = :id diff --git a/resources/sql/queries.sql b/resources/sql/queries.sql index e2170b8..9735513 100644 --- a/resources/sql/queries.sql +++ b/resources/sql/queries.sql @@ -1,6 +1,6 @@ ------------------------------------------------------------------------------; ---- ----- youyesyet.routes.authenticated: routes and pages for authenticated users. +---- queries.sql: manually maintained queries. ---- ---- This program is free software; you can redistribute it and/or ---- modify it under the terms of the GNU General Public License @@ -23,322 +23,17 @@ -- 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") +-- (conman/bind-connection *db* "sql/queries-auto.sql" "sql/queries.sql") -- the functions then appeare in the youyesyet.db.core namespace. +-- Note that queries generated by ADL are in the file +-- resources/sql/queries-auto.sql; they do not have to be (and should not be) +-- redefined here. --- :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 list-addresses-by-locality :? :* +-- :doc lists all existing address records in a given locality +SELECT * +FROM addresses +WHERE locality = :locality --- :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-address :? :1 --- :doc retrieve a address given the id. -SELECT * FROM addresses -WHERE id = :id - --- :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/src/clj/youyesyet/db/core.clj b/src/clj/youyesyet/db/core.clj index ccbb495..5cb0a7d 100644 --- a/src/clj/youyesyet/db/core.clj +++ b/src/clj/youyesyet/db/core.clj @@ -22,7 +22,7 @@ :driver-class-name "org.postgresql.Driver"}) :stop (conman/disconnect! *db*)) -(conman/bind-connection *db* "sql/queries.auto.sql") +(conman/bind-connection *db* "sql/queries.auto.sql" "sql/queries.sql") (defn to-date [^java.sql.Date sql-date] (-> sql-date (.getTime) (java.util.Date.))) diff --git a/src/clj/youyesyet/handler.clj b/src/clj/youyesyet/handler.clj index c9c8119..6027a28 100644 --- a/src/clj/youyesyet/handler.clj +++ b/src/clj/youyesyet/handler.clj @@ -13,6 +13,7 @@ [youyesyet.routes.oauth :refer [oauth-routes]] [youyesyet.routes.auto-json :refer [auto-rest-routes]] [youyesyet.routes.auto :refer [auto-selmer-routes]] + [youyesyet.routes.rest :refer [rest-routes]] [youyesyet.env :refer [defaults]])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -71,6 +72,8 @@ (-> #'auto-selmer-routes (wrap-routes middleware/wrap-csrf) (wrap-routes middleware/wrap-formats)) + (-> #'rest-routes + (wrap-routes middleware/wrap-formats)) 'oauth-routes (route/resources "/") (route/not-found diff --git a/src/clj/youyesyet/routes/rest.clj b/src/clj/youyesyet/routes/rest.clj index 9028b3d..3e4093e 100644 --- a/src/clj/youyesyet/routes/rest.clj +++ b/src/clj/youyesyet/routes/rest.clj @@ -3,10 +3,13 @@ (:require [clojure.walk :refer [keywordize-keys]] [noir.response :as nresponse] [noir.util.route :as route] - [youyesyet.db.core :as db-core] + [youyesyet.db.core :as db] [compojure.core :refer [defroutes GET POST]] [ring.util.http-response :as response] - [clojure.java.io :as io])) + [clojure.java.io :as io] + [youyesyet.locality :as l] + [youyesyet.utils :refer :all] + )) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; @@ -36,14 +39,29 @@ "Get data local to the user of the canvasser app. Expects arguments `lat` and `long`. Returns a block of data for that locality" [request] - ) + (let + [{latitude :latitude longitude :longitude} (keywordize-keys (:params request)) + neighbourhood (l/neighbouring-localities + (l/locality + (coerce-to-number latitude) + (coerce-to-number longitude))) + addresses (flatten + (map + #(db/list-addresses-by-locality db/*db* {:locality %}) + neighbourhood))] + + addresses + )) + + (defn get-issues "Get current issues. No arguments expected." [request]) -;; (defroutes rest-routes -;; (GET "/rest/get-local-data" request (route/restricted (get-local-data request))) +(defroutes rest-routes + (GET "/rest/get-local-data" request (get-local-data request)) ;; (GET "/rest/get-issues" request (route/restricted (get-issues request))) ;; (GET "/rest/set-intention" request (route/restricted (set-intention request))) ;; (GET "/rest/request-followup" request (route/restricted (request-followup request)))) +) diff --git a/src/cljc/youyesyet/locality.cljc b/src/cljc/youyesyet/locality.cljc new file mode 100644 index 0000000..dec0417 --- /dev/null +++ b/src/cljc/youyesyet/locality.cljc @@ -0,0 +1,54 @@ +(ns youyesyet.locality) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; youyesyet.locality: compute localities. +;;;; +;;;; 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 +;;;; +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;;;; See also resources/sql/locality-trigger.sql + +(defn locality + "Compute the locality index for this `latitude`/`longitude` pair." + [latitude longitude] + (+ + (* 10000 ;; left-shift the latitude component four digits + (int + (* latitude 1000))) + (- ;; invert the sign of the longitude component, since + (int ;; we're interested in localities West of Greenwich. + (* longitude 1000))))) + +(defn neighbouring-localities + "Return this locality with the localities immediately + north east, north, north west, east, west, south west, + south and south east of it." + ;; TODO: I'm not absolutely confident of my arithmetic here! + [locality] + (list + (- locality 9999) + (- locality 10000) + (- locality 10001) + (- locality 1) + locality + (+ locality 1) + (+ locality 9999) + (+ locality 10000) + (+ locality 10001))) diff --git a/src/cljc/youyesyet/utils.cljc b/src/cljc/youyesyet/utils.cljc new file mode 100644 index 0000000..41c9cb9 --- /dev/null +++ b/src/cljc/youyesyet/utils.cljc @@ -0,0 +1,35 @@ +(ns youyesyet.utils + #?(:clj (require [clojure.tools.logging :as log])) + ) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; youyesyet.locality: small utility functions. +;;;; +;;;; 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 coerce-to-number [v] + "If it is possible to do so, coerce `v` to a number" + (if (number? v) v + (try + (read-string (str v)) + #?(:clj (catch Exception any + (log/error (str "Could not coerce '" v "' to number: " any))) + :cljs (js/console.log (str "Could not coerce '" v "' to number: " any)))))) diff --git a/src/cljs/youyesyet/canvasser_app/core.cljs b/src/cljs/youyesyet/canvasser_app/core.cljs index fe5028c..e1e4347 100644 --- a/src/cljs/youyesyet/canvasser_app/core.cljs +++ b/src/cljs/youyesyet/canvasser_app/core.cljs @@ -134,6 +134,18 @@ (secretary/defroute "/building/:address" {address-id :address} (log-and-dispatch [:set-address address-id])) +(secretary/defroute "/elector" [] + (log-and-dispatch [:set-active-page :elector])) + +(secretary/defroute "/elector/:elector" {elector-id :elector} + (log-and-dispatch [:set-elector-and-page {:elector-id elector-id :page :elector}])) + +(secretary/defroute "/elector/:elector/:consent" {elector-id :elector consent :consent} + (log-and-dispatch [:set-consent-and-page {:elector-id elector-id :consent (and true consent) :page :elector}])) + +(secretary/defroute "/elector" [] + (log-and-dispatch [:set-active-page :elector])) + (secretary/defroute "/followup" [] (log-and-dispatch [:set-active-page :followup])) diff --git a/src/cljs/youyesyet/canvasser_app/handlers.cljs b/src/cljs/youyesyet/canvasser_app/handlers.cljs index 62e8f82..45edcd1 100644 --- a/src/cljs/youyesyet/canvasser_app/handlers.cljs +++ b/src/cljs/youyesyet/canvasser_app/handlers.cljs @@ -4,6 +4,7 @@ (:require [cljs.reader :refer [read-string]] [re-frame.core :refer [dispatch reg-event-db]] [youyesyet.canvasser-app.state :as db] + [youyesyet.utils :refer :all] )) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -36,12 +37,6 @@ (merge state {:error nil :feedback nil})) -(defn coerce-to-number [v] - (if (number? v) v - (try - (read-string (str v)) - (catch js/Object any - (js/console.log (str "Could not coerce '" v "' to number: " any)))))) (defn get-elector @@ -169,9 +164,10 @@ :set-consent-and-page (fn [db [_ args]] (let [page (:page args) + consent (:consent args) elector-id (coerce-to-number (:elector-id args)) elector (get-elector elector-id db)] - (js/console.log (str "Setting page to " page ", consent to true for " elector)) + (js/console.log (str "Setting page to " page ", consent to " consent " for " elector)) (assoc (clear-messages db) :elector (assoc elector :consent true) :page page)))) diff --git a/src/cljs/youyesyet/canvasser_app/ui_utils.cljs b/src/cljs/youyesyet/canvasser_app/ui_utils.cljs index 4aa9887..2f6a52b 100644 --- a/src/cljs/youyesyet/canvasser_app/ui_utils.cljs +++ b/src/cljs/youyesyet/canvasser_app/ui_utils.cljs @@ -39,6 +39,7 @@ (defn big-link [text & {:keys [target handler]}] + (js/console.log (str "Constructing big link; target is '" target "'; handler is '" handler "'")) [:div.big-link-container {:key (gensym "big-link")} [:a.big-link (merge (if target {:href target}{}) diff --git a/src/cljs/youyesyet/canvasser_app/views/elector.cljs b/src/cljs/youyesyet/canvasser_app/views/elector.cljs index 920fb11..89d132c 100644 --- a/src/cljs/youyesyet/canvasser_app/views/elector.cljs +++ b/src/cljs/youyesyet/canvasser_app/views/elector.cljs @@ -64,6 +64,7 @@ [:send-intention {:elector-id (:id elector) :intention optid}])}]])])) + (defn issue-row "Generate a row containing an issue cell for a particular elector" [elector] @@ -76,25 +77,17 @@ (defn panel "Generate the elector panel." [] - (let [address @(subscribe [:address]) - dwelling @(subscribe [:dwelling]) - elector @(subscribe [:elector]) - electors [elector] - options @(subscribe [:options]) - sub-address (:sub-address dwelling)] - (if address + (let [elector @(subscribe [:elector]) + options @(subscribe [:options])] + (if elector [:div - [:h1 (if sub-address - (str sub-address ", " (:address address)) - (:address address))] + [:h1 (:name elector)] [:div.container {:id "main-container"} [:table [:tbody - (gender-row elector) - (name-row elector) (map #(option-row elector %) options) (issue-row elector)]] - (ui/back-link)]] - (ui/error-panel "No address selected")))) + (ui/back-link "#dwelling")]] + (ui/error-panel "No elector selected")))) diff --git a/src/cljs/youyesyet/canvasser_app/views/followup.cljs b/src/cljs/youyesyet/canvasser_app/views/followup.cljs index b81ff58..385ffe4 100644 --- a/src/cljs/youyesyet/canvasser_app/views/followup.cljs +++ b/src/cljs/youyesyet/canvasser_app/views/followup.cljs @@ -41,11 +41,11 @@ (let [issue @(subscribe [:issue]) issues @(subscribe [:issues]) elector @(subscribe [:elector]) - address @(subscribe [:address])] + dwelling @(subscribe [:dwelling])] (js/console.log (str "Issue is " issue "; elector is " elector)) (cond - (nil? address) - (ui/error-panel "No address selected") + (nil? dwelling) + (ui/error-panel "No dwelling selected") (nil? issues) (ui/error-panel "No issues loaded") true @@ -59,7 +59,7 @@ :on-change #(dispatch [:set-elector (.-value (.-target %))])} (map #(let [] - [:option {:value (:id %) :key (:id %)} (:name %)]) (:electors address))]] + [:option {:value (:id %) :key (:id %)} (:name %)]) (:electors dwelling))]] [:p.widget [:label {:for "issue"} "Issue"] ;; #(reset! val (-> % .-target .-value)) diff --git a/src/cljs/youyesyet/canvasser_app/views/gdpr.cljs b/src/cljs/youyesyet/canvasser_app/views/gdpr.cljs index cb933fd..65e1e5a 100644 --- a/src/cljs/youyesyet/canvasser_app/views/gdpr.cljs +++ b/src/cljs/youyesyet/canvasser_app/views/gdpr.cljs @@ -34,12 +34,10 @@ [] (let [elector @(subscribe [:elector])] [:div - [:h1 "GDPR Consent"] + [:h1 "I, " (:name elector)] [:div.container {:id "main-container"} [:table [:tbody - [:tr - [:th "I," (:name elector)]] [:tr [:td [:p "Consent to have data about my voting intention stored by " @@ -53,13 +51,12 @@ [:canvas {:id "signature-pad"}]]]]]] (ui/back-link "#dwelling") (ui/big-link "I consent" - :handler #(fn [] (dispatch [:set-consent-and-page {:elector-id (:id elector) - :page :elector}]))) + :target (str "#elector/" (:id elector) "/true") + :handler #(fn [] (dispatch [:set-consent-and-page {:elector-id (:id elector) :page :elector}]))) ;; TODO: need to save the signature (ui/big-link "I DO NOT consent" - :handler - #(fn [] (dispatch [:set-elector-and-page {:elector-id (:id elector) - :page :elector}])))])) + :target (str "#elector/" (:id elector) "/true"))])) + ;; :handler #(fn [] (dispatch [:set-elector-and-page {:elector-id (:id elector) :page :elector}])))])) (defn gdpr-did-mount