diff --git a/project.clj b/project.clj index cb7c458..1a41985 100644 --- a/project.clj +++ b/project.clj @@ -54,7 +54,7 @@ :main ^:skip-aot youyesyet.core :migratus {:store :database :db ~(get (System/getenv) "DATABASE_URL")} - :plugins [[lein-adl ["0.1.1"]] + :plugins [;;[lein-adl ["0.1.1"]] [lein-cljsbuild "1.1.4"] [lein-codox "0.10.3"] [lein-cprop "1.0.1"] diff --git a/resources/sql/youyesyet.postgres.sql b/resources/sql/youyesyet.postgres.sql index d50afd7..0c9f60c 100644 --- a/resources/sql/youyesyet.postgres.sql +++ b/resources/sql/youyesyet.postgres.sql @@ -5,7 +5,7 @@ -- -- auto-generated by [Application Description Language framework] -- --- (https://github.com/simon-brooke/adl) at 20180721T111020.637Z +-- (https://github.com/simon-brooke/adl) at 20180721T133846.002Z -- -- A web-app intended to be used by canvassers -- campaigning for a 'Yes' vote in the second independence @@ -72,7 +72,7 @@ CREATE TABLE addresses ( id SERIAL NOT NULL PRIMARY KEY, address VARCHAR(256) NOT NULL, - postcode VARCHAR(16) CONSTRAINT pattern_7470 CHECK (postcode ~* '^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z]))))[0-9][A-Za-z]{2})$'), + postcode VARCHAR(16) CONSTRAINT pattern_1 CHECK (postcode ~* '^([Gg][Ii][Rr] 0[Aa]{2})|((([A-Za-z][0-9]{1,2})|(([A-Za-z][A-Ha-hJ-Yj-y][0-9]{1,2})|(([AZa-z][0-9][A-Za-z])|([A-Za-z][A-Ha-hJ-Yj-y][0-9]?[A-Za-z]))))[0-9][A-Za-z]{2})$'), phone VARCHAR(16), district_id INTEGER, latitude DOUBLE PRECISION, @@ -985,31 +985,33 @@ ALTER TABLE ln_roles_canvassers_roles ADD CONSTRAINT ri_ln_roles_canvassers_role ON DELETE NO ACTION ; ------------------------------------------------------------------------ --- link table joining roles with canvassers +-- link table joining events with teams ------------------------------------------------------------------------ -CREATE TABLE ln_members_roles_canvassers +CREATE TABLE ln_teams_events_teams ( - role_id INTEGER, - canvasser_id INTEGER + event_id INTEGER, + team_id INTEGER ); -GRANT SELECT ON ln_members_roles_canvassers TO admin, +GRANT SELECT ON ln_teams_events_teams TO admin, analysts, canvassers, issueeditors, issueexperts, teamorganisers ; -GRANT INSERT ON ln_members_roles_canvassers TO admin ; -GRANT UPDATE ON ln_members_roles_canvassers TO admin ; -GRANT DELETE ON ln_members_roles_canvassers TO admin ; +GRANT INSERT ON ln_teams_events_teams TO admin, + teamorganisers ; +GRANT UPDATE ON ln_teams_events_teams TO admin, + teamorganisers ; +GRANT DELETE ON ln_teams_events_teams TO admin ; -ALTER TABLE ln_members_roles_canvassers ADD CONSTRAINT ri_ln_members_roles_canvassers_canvassers_canvasser_id - FOREIGN KEY( canvasser_id ) - REFERENCES canvassers(id) +ALTER TABLE ln_teams_events_teams ADD CONSTRAINT ri_ln_teams_events_teams_events_event_id + FOREIGN KEY( event_id ) + REFERENCES events(id) ON DELETE NO ACTION ; -ALTER TABLE ln_members_roles_canvassers ADD CONSTRAINT ri_ln_members_roles_canvassers_roles_role_id - FOREIGN KEY( role_id ) - REFERENCES roles(id) +ALTER TABLE ln_teams_events_teams ADD CONSTRAINT ri_ln_teams_events_teams_teams_team_id + FOREIGN KEY( team_id ) + REFERENCES teams(id) ON DELETE NO ACTION ; ------------------------------------------------------------------------ diff --git a/resources/templates/base.html b/resources/templates/base.html index 54b4f02..26ecb00 100644 --- a/resources/templates/base.html +++ b/resources/templates/base.html @@ -63,6 +63,9 @@ {% endblock %} +

+ User: {{user}} +


{% block foot %} diff --git a/src/clj/youyesyet/layout.clj b/src/clj/youyesyet/layout.clj index a784cc6..06f56f3 100644 --- a/src/clj/youyesyet/layout.clj +++ b/src/clj/youyesyet/layout.clj @@ -39,28 +39,29 @@ (declare ^:dynamic *app-context*) +(def ^:dynamic *user* nil) + (parser/set-resource-path! (clojure.java.io/resource "templates")) (parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field))) (filters/add-filter! :markdown (fn [content] [:safe (md-to-html-string content)])) (tags/add-tags) -(defn raw-get-user-roles [user] - "Return, as a set, the names of the roles of which this user is a member." - (if - user - (do - (log/debug (str "seeking roles for user " user)) - (let [roles - (set (map #(lower-case (:name %)) (db/list-roles-by-canvasser db/*db* user)))] - (log/debug (str "found roles " roles " for user " user)) - roles)))) - - ;; role assignments change only rarely. -(def get-user-roles (memoize raw-get-user-roles)) +(def get-user-roles + "Return, as a set, the names of the roles of which this `user` is a member." + (memoize + (fn [user] + (if + user + (do + (log/debug (str "seeking roles for user " user)) + (let [roles + (set (map #(lower-case (:name %)) (db/list-roles-by-canvasser db/*db* user)))] + (log/debug (str "found roles " roles " for user " user)) + roles)))))) -(defn render +(defn render-with-session "renders the HTML `template` located relative to resources/templates in the context of this session and with these parameters." ;; TODO: I'm passing `session` through into render. The default luminus @@ -68,7 +69,7 @@ ;; than me so there's almost certainly a reason it doesn't. [template session & [params]] (let [user (:user session)] - (log/debug (str "layout/render: template: '" template "'")) + (log/debug (str "layout/render-with-session: template: '" template "'")) (content-type (ok (parser/render-file @@ -83,6 +84,26 @@ "text/html; charset=utf-8"))) +(defn render + "renders the HTML `template` located relative to resources/templates in + the context of this session and with these parameters." + [template & [params]] + (log/debug (str "layout/render: template: '" template "'")) + (content-type + (ok + (parser/render-file + template + (merge params + {:page template + :csrf-token *anti-forgery-token* + :user *user* + :user-roles (get-user-roles *user*) + :site-title (:site-title env) + :version (System/getProperty "youyesyet.version")}))) + "text/html; charset=utf-8")) + + + (defn error-page "error-details should be a map containing the following keys: :status - error status diff --git a/src/clj/youyesyet/middleware.clj b/src/clj/youyesyet/middleware.clj index 7cbad0a..5e28726 100644 --- a/src/clj/youyesyet/middleware.clj +++ b/src/clj/youyesyet/middleware.clj @@ -8,7 +8,7 @@ [ring-ttl-session.core :refer [ttl-memory-store]] [youyesyet.env :refer [defaults]] [youyesyet.config :refer [env]] - [youyesyet.layout :refer [*app-context* error-page]]) + [youyesyet.layout :refer [*app-context* *user* error-page]]) (:import [javax.servlet ServletContext])) @@ -63,6 +63,17 @@ ((if (:websocket? request) handler wrapped) request)))) +(defn wrap-user + "Dynamically bind *user* to the user in the session, if any, so that it + is available in layout/render, q.v." + [handler] + (fn [request] + (if-let [user (-> request :session :user)] + (binding [*user* user] + (handler request)) + (handler request)))) + + (defn wrap-base [handler] (-> ((:middleware defaults) handler) wrap-webjars @@ -71,4 +82,6 @@ (assoc-in [:security :anti-forgery] false) (assoc-in [:session :store] (ttl-memory-store (* 60 30))))) wrap-context - wrap-internal-error)) + wrap-internal-error + wrap-user)) + diff --git a/src/clj/youyesyet/routes/home.clj b/src/clj/youyesyet/routes/home.clj index 2ad737f..68408f5 100644 --- a/src/clj/youyesyet/routes/home.clj +++ b/src/clj/youyesyet/routes/home.clj @@ -49,7 +49,7 @@ (defn about-page [] - (layout/render "about.html" {} {:title + (layout/render "about.html" {:title (str "About " (:site-title env)) :motd (md-to-html-string (slurp (io/resource "about.md")))})) @@ -67,7 +67,7 @@ (defn home-page [] - (layout/render "home.html" {} {:title "You yes yet?" + (layout/render "home.html" {:title "You yes yet?" :motd (md-to-html-string (motd))})) @@ -100,13 +100,12 @@ (assoc (response/found redirect-to) :session - (assoc session :user user :roles roles))) + (assoc session :user (assoc user :roles roles)))) ;; if we've got a username but either no user object or else ;; the password doesn't match username (layout/render "login.html" - session {:title (str "User " username " is unknown") :redirect-to redirect-to :warnings ["Your user name was not recognised or your password did not match"]}) @@ -114,7 +113,6 @@ true (layout/render "login.html" - session {:title "Please log in" :redirect-to redirect-to :authorities (db-core/list-authorities db-core/*db*)})))) diff --git a/src/clj/youyesyet/routes/issue_experts.clj b/src/clj/youyesyet/routes/issue_experts.clj index 8a54566..f73b801 100644 --- a/src/clj/youyesyet/routes/issue_experts.clj +++ b/src/clj/youyesyet/routes/issue_experts.clj @@ -42,7 +42,7 @@ (defn list-page [request] (layout/render "issue-expert/list.html" - (:session request) +;; (:session request) (let [user (:user (:session request))] {:title "Open requests" :user user @@ -69,7 +69,7 @@ db/*db* {:id (:visit_id record)})))] (layout/render "issue-expert/request.html" - (:session request) + ;; (:session request) {:title (str "Request from " (:name elector) " at " (:date visit)) :user (:user (:session request)) :visit visit diff --git a/src/clj/youyesyet/routes/logged_in.clj b/src/clj/youyesyet/routes/logged_in.clj index c334d08..1c4c140 100644 --- a/src/clj/youyesyet/routes/logged_in.clj +++ b/src/clj/youyesyet/routes/logged_in.clj @@ -42,7 +42,6 @@ (defn app-page [request] (layout/render "app.html" - (:session request) {:title "Canvasser app"})) @@ -51,7 +50,7 @@ (let [record (-> request :session :user)] (layout/render "auto/form-canvassers-Canvasser.html" - (:session request) +;; (:session request) {:title (str "Profile for " (-> request :session :user :fullname)) :record record :elector_id diff --git a/src/clj/youyesyet/routes/roles.clj b/src/clj/youyesyet/routes/roles.clj index e2cae2b..851bc58 100644 --- a/src/clj/youyesyet/routes/roles.clj +++ b/src/clj/youyesyet/routes/roles.clj @@ -19,14 +19,14 @@ "Render the routing page for the roles the currently logged in user is member of." (let [session (:session request) - user (:user session) + user (-> request :session :user) roles (if user (db-core/list-roles-by-canvasser db-core/*db* {:id (:id user)}))] (log/info (str "Roles routing page; user is " user "; roles are " roles)) (cond roles (layout/render "roles.html" - (:session request) +;; ;; (:session request) {:title (str "Welcome " (:fullname user) ", what do you want to do?") :user user :roles (map #(assoc % :link (safe-name (:name %) :sql)) roles)}) @@ -38,7 +38,7 @@ [request] (layout/render (support/resolve-template "application-index.html") - (:session request) +;; (:session request) {:title "Administrative menu"})) @@ -49,18 +49,24 @@ [request] (layout/render (support/resolve-template "application-index.html") - (:session request) +;; (:session request) {:title "Administrative menu"})) (defn canvassers-page [request] - (layout/render "roles/canvasser.html" (:session request) {})) + (layout/render + "roles/canvasser.html" +;; (:session request) + {})) (defn team-organisers-page [request] - (layout/render "roles/team-orgenisers.html" request {})) + (layout/render + "roles/team-orgenisers.html" +;; request + {})) (defroutes roles-routes diff --git a/youyesyet.canonical.adl.xml b/youyesyet.canonical.adl.xml index fb58539..08e3a63 100644 --- a/youyesyet.canonical.adl.xml +++ b/youyesyet.canonical.adl.xml @@ -14,8 +14,7 @@ * Generated using adl2canonical.xslt 1.10 $ * *************************************************************************** - --> - A web-app intended to be used by canvassers + -->A web-app intended to be used by canvassers campaigning for a 'Yes' vote in the second independence referendum. The web-app will be delivered to canvassers out knocking doors primarily through an HTML5/React single-page app @@ -26,225 +25,236 @@ set the system up and authorise canvassers, and a 'followup' interface through which issue-expert specialist canvassers can address particular electors' queries. - - See + + See https://assets.publishing.service.gov.uk/government/uploads/system/uploads/attachment_data/file/488478/Bulk_Data_Transfer_-_additional_validation_valid_from_12_November_2015.pdf, section 3 - A valid postcode. + A valid postcode. All users - - All users of the canvasser app Able to read and + + All users of the canvasser app Able to read and add canvassing data in a limited radius around their current position. - - Organisers of canvassing teams Able to see and + + Organisers of canvassing teams Able to see and modify data on the canvassers in the team(s) they organise; able to add canvassers to their team; able to update canvassers in their team, including resetting passwords and locking accounts; able to see canvass data over the whole area in which their team operates. - - People expert on particular issues. Able to read + + People expert on particular issues. Able to read followup requests, and the electors to which they relate; able to access (read/write) the issues wiki; able to write followuop action records. - - Users entitled to see an overview of the + + Users entitled to see an overview of the canvassing data collected. Able to read canvassing data over the whole map, including historical data. - - Users responsible for determining what issues + + Users responsible for determining what issues should be current at any time. Able to set current issues; able to add issues. - - Able to read and update canvasser records, team + + Able to read and update canvasser records, team membership records, team organisership records, issue expertise records; able to add and update reference data generally. - - All electors known to the system; electors are + --> + All electors known to the system; electors are people believed to be entitled to vote in the current campaign. - - - + + + + + + + - - - - - - - - - - - - - - - - - - The signature of this elector, captured as SVG text, + + + + + + + + + + + + + + The signature of this elector, captured as SVG text, as evidence they have consented to us holding data on them. Null if they have not. - - - - - - - - - -
- - - - - - - - - - - - - - - - - - -
- - - - - - +
+ + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + + + + +
- - All genders which may be assigned to + --> + All genders which may be assigned to electors. - - - - - - - - - - -
- - - -
- - - - - - + + + + + + + + + + +
+ + + +
+ + + + + +
- - All dwellings within addresses in the system; a + --> + All dwellings within addresses in the system; a dwelling is a house, flat or appartment in which electors live. Every address should have at least one dwelling; essentially, an address maps onto a street door and dwellings map onto what's behind that door. So a tenement or a block of flats would be one address with many dwellings. - - - + + + + + + + - - - - - - + + The part of the address which identifies the flat or apartment within the building, if in a multiple occupancy building. - - - - - - - - - -
- - - - - - -
- - - - - - +
+ + + + + + + + +
+ + + + + + +
+ + + + + +
- - Addresses of all buildings which contain + --> + Addresses of all buildings which contain dwellings. - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - Locality indexing; see issue #44. Note that + Locality indexing; see issue #44. Note that this property should be generated automatically from the latitude and longitude: (+ (* 10000 ;; left-shift the latitude component four digits (integer (* latitude 1000))) @@ -254,802 +264,845 @@ don't think it will ever appear in the user interface; it's an implementation detail, not of interest to users. - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + +
- - All visits made by canvassers to dwellings in + --> + All visits made by canvassers to dwellings in which opinions were recorded. - - - + + + + + + + + - - - - - - - - - - - - - But only in their immediate + + + + + + + + But only in their immediate area. - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - -
- - - - - - - + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + +
- - Authorities which may authenticate canvassers to + --> + Authorities which may authenticate canvassers to the system. - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - -
- - Issues believed to be of interest to electors, + --> + Issues believed to be of interest to electors, about which they may have questions. - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - -
- - - - - - + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + +
- - - Intentions of electors to vote for options + --> + Intentions of electors to vote for options elicited in visits. - - - + + + + + + + - - - - - - - - - - - - The locality at which the intention was + + + + + + + + The locality at which the intention was recorded; used where an elector does not consent to have polling intention stored against them. This is a locality as described in - - - - - - - - - - - - - - - - - - - - The locality at which the intention was - recorded; used where an elector does not consent to have - polling intention stored against them. This is a locality - as described in - - -
- - - - - - - - - - - The locality at which the intention was - recorded; used where an elector does not consent to have - polling intention stored against them. This is a locality - as described in - -
-
- - - Primary users of the system: those actually - interviewing electors. - - - - - - - - - - - - An image of the canvasser, so that other members of their + + + + + + + + + + + + + + + + + + + The locality at which the intention was + recorded; used where an elector does not consent to have + polling intention stored against them. This is a locality + as described in + + +
+ + + + + + + + + + + The locality at which the intention was + recorded; used where an elector does not consent to have + polling intention stored against them. This is a locality + as described in + +
+
+ + Primary users of the system: those actually + interviewing electors. + + + + + + + + + + + + + An image of the canvasser, so that other members of their team can recognise them. - - - - Information the canvasser supplies about themselves; an introduction. - - - - - - - - - - - - - - - - - - - - - - - - - Only relevant to issue experts. - - - - But only their own record - - - But only canvassers in their own + + + + Information the canvasser supplies about themselves; an introduction. + + + + + + + + + + + + + + + + + + + + + + + + + Only relevant to issue experts. + + + + But only their own record + + + But only canvassers in their own team. - - - All canvassers - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - But should only be able to edit their own + + + All canvassers + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + But should only be able to edit their own record. - - - - - - + + + + + +
- - Requests for a followup with an issue + --> + Requests for a followup with an issue expert - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - -
- - - - - - + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+ + + + + +
- - A role (essentially, the same as a group, but + --> + A role (essentially, the same as a group, but application layer rather than database layer) of which a user may be a member. - - - + + + + + + + - - - - - - - - - - - - -
- - - - - - - - - -
- - - - - - + + + + + + + + +
+ + + + + + + + + +
+ + + + + +
- - - - + --> + + + + + + + - - - - - - - - - - - - - - - - - - - - - - But only their own group(s) - - - - - - All groups - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + But only their own group(s) + + + + + + All groups + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + +
- - - + --> + An event to which a team or teams are invited. Typically created by the team organiser(s). May be a training event, a social event or a canvassing session. - - - + + + + + + + - - - - - - - - - - - - - - - - -
- - - - - - + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + +
- - - Electoral districts: TODO: Shape (polygon) + --> + Electoral districts: TODO: Shape (polygon) information will need to be added, for use in maps. - - - + + + + + + + - - - - - - - - - - - -
- - - -
- - - - - - + + + + + + + +
+ + + +
+ + + + + +
- - Actions taken on followup + --> + Actions taken on followup requests. - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - -
- - - But only for electors in their immediate + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + +
+ + + But only for electors in their immediate vicinity - - - - - + + + + +
- - Options in the election or referendum being + --> + Options in the election or referendum being canvassed on - - - - - - - - - - -
- - - -
- - - - - - + + + + + + + + + + +
+ + + +
+ + + + + +
- - Methods which may be used to follow up a followup request. Reference data. - - - - - - - - - - -
- - - -
- - - - - - + --> + Methods which may be used to follow up a followup request. Reference data. + + + + + + + + + + +
+ + + +
+ + + + + +
\ No newline at end of file