Skip to content

Commit 07b4645

Browse files
authored
Merge pull request #840 from clojars/tobias/group-membership-emails
2 parents 0d17094 + 936c9e0 commit 07b4645

File tree

6 files changed

+191
-72
lines changed

6 files changed

+191
-72
lines changed

resources/queries/queryfile.sql

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ JOIN (
111111
) g
112112
ON (
113113
u.user = g.user
114-
)
114+
);
115115

116116
--name: group-allnames
117117
SELECT "user"
@@ -141,6 +141,24 @@ WHERE (
141141
admin = true
142142
);
143143

144+
--name: group-admin-emails
145+
SELECT u.email
146+
FROM users u
147+
JOIN (
148+
SELECT "user"
149+
FROM groups
150+
WHERE (
151+
name = :groupname
152+
AND
153+
inactive IS NOT true
154+
AND
155+
admin = true
156+
)
157+
) g
158+
ON (
159+
u.user = g.user
160+
);
161+
144162
--name: inactivate-member!
145163
UPDATE groups
146164
SET inactive = true, inactivated_by = :inactivated_by

src/clojars/db.clj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,11 @@
197197
{:connection db
198198
:row-fn :user}))
199199

200+
(defn group-admin-emails [db groupname]
201+
(sql/group-admin-emails {:groupname groupname}
202+
{:connection db
203+
:row-fn :email}))
204+
200205
(defn group-activenames [db groupname]
201206
(sql/group-activenames {:groupname groupname}
202207
{:connection db
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
(ns clojars.notifications.group
2+
(:require
3+
[clojars.notifications :as notifications]
4+
[clojars.notifications.common :as common]))
5+
6+
(defmethod notifications/notification :group-member-added
7+
[_type mailer
8+
{:as _user username :user}
9+
{:as data :keys [admin? group member admin-emails member-email]}]
10+
(let [subject (format "A%s member was added to the group %s"
11+
(if admin? "n admin" "")
12+
group)
13+
body [(format
14+
"User '%s' was added%s to the %s group by %s."
15+
member
16+
(if admin? " as an admin" "")
17+
group
18+
username)
19+
(common/details-table data)]
20+
emails (set (concat admin-emails [member-email]))]
21+
(doseq [email emails]
22+
(notifications/send mailer email subject body))))
23+
24+
(defmethod notifications/notification :group-member-removed
25+
[_type mailer
26+
{:as _user username :user}
27+
{:as data :keys [group member admin-emails member-email]}]
28+
(let [subject (format "A member was removed from the group %s"
29+
group)
30+
body [(format
31+
"User '%s' was removed from the %s group by %s."
32+
member
33+
group
34+
username)
35+
(common/details-table data)]
36+
emails (set (concat admin-emails [member-email]))]
37+
(doseq [email emails]
38+
(notifications/send mailer email subject body))))

src/clojars/routes/group.clj

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,27 @@
22
(:require
33
[clojars.auth :as auth]
44
[clojars.db :as db]
5+
[clojars.event :as event]
6+
[clojars.log :as log]
7+
[clojars.routes.common :as common]
58
[clojars.web.group :as view]
6-
[compojure.core :as compojure :refer [GET POST DELETE]]
7-
[clojars.log :as log]))
9+
[compojure.core :as compojure :refer [GET POST DELETE]]))
810

911
(defn- get-members [db groupname]
1012
(let [actives (seq (db/group-actives db groupname))]
1113
(when (seq actives)
1214
(auth/try-account
1315
#(view/show-group db % groupname actives)))))
1416

15-
(defn- toggle-or-add-member [db groupname username make-admin?]
17+
(defn- toggle-or-add-member [db groupname username make-admin? details]
1618
(let [actives (seq (db/group-actives db groupname))
1719
membernames (->> actives
1820
(filter (complement view/is-admin?))
1921
(map :user))
2022
adminnames (->> actives
2123
(filter view/is-admin?)
2224
(map :user))
25+
user-to-add (db/find-user db username)
2326
handler-fn (fn [account admin? group-users]
2427
#(log/with-context {:tag :toggle-or-add-group-member
2528
:group groupname
@@ -41,9 +44,18 @@
4144
(view/show-group db account groupname actives
4245
(format "They're already an %s!" (if admin? "admin" "member"))))
4346

44-
(db/find-user db username)
47+
user-to-add
4548
(let [add-fn (if admin? db/add-admin db/add-member)]
4649
(add-fn db groupname username account)
50+
(event/emit :group-member-added
51+
(merge
52+
details
53+
{:admin? admin?
54+
:admin-emails (db/group-admin-emails db groupname)
55+
:group groupname
56+
:member username
57+
:member-email (:email user-to-add)
58+
:username account}))
4759
(log/info {:status :success})
4860
(log/audit db {:tag :member-added
4961
:message (format "user '%s' added as %s" username
@@ -71,7 +83,7 @@
7183
make-admin?
7284
(if make-admin? adminnames membernames))))))))
7385

74-
(defn- remove-member [db groupname username]
86+
(defn- remove-member [db groupname username details]
7587
(let [actives (seq (db/group-actives db groupname))]
7688
(when (seq actives)
7789
(auth/try-account
@@ -95,6 +107,14 @@
95107
(some #{username} (map :user actives))
96108
(do
97109
(db/inactivate-member db groupname username account)
110+
(event/emit :group-member-removed
111+
(merge
112+
details
113+
{:admin-emails (db/group-admin-emails db groupname)
114+
:group groupname
115+
:member username
116+
:member-email (:email (db/find-user db username))
117+
:username account}))
98118
(log/info {:status :success})
99119
(log/audit db {:tag :member-removed
100120
:message (format "user '%s' removed" username)})
@@ -113,7 +133,7 @@
113133
(compojure/routes
114134
(GET ["/groups/:groupname", :groupname #"[^/]+"] [groupname]
115135
(get-members db groupname))
116-
(POST ["/groups/:groupname", :groupname #"[^/]+"] [groupname username admin]
117-
(toggle-or-add-member db groupname username (= "1" admin)))
118-
(DELETE ["/groups/:groupname", :groupname #"[^/]+"] [groupname username]
119-
(remove-member db groupname username))))
136+
(POST ["/groups/:groupname", :groupname #"[^/]+"] [groupname username admin :as request]
137+
(toggle-or-add-member db groupname username (= "1" admin) (common/request-details request)))
138+
(DELETE ["/groups/:groupname", :groupname #"[^/]+"] [groupname username :as request]
139+
(remove-member db groupname username (common/request-details request)))))

src/clojars/system.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[clojars.notifications :as notifications]
55
;; for defmethods
66
[clojars.notifications.deploys]
7+
[clojars.notifications.group]
78
[clojars.notifications.token]
89
[clojars.notifications.user]
910
[clojars.oauth.github :as github]

test/clojars/integration/users_test.clj

Lines changed: 99 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
[clojars.email :as email]
55
[clojars.integration.steps :refer [disable-mfa enable-mfa login-as register-as]]
66
;; for defmethods
7+
[clojars.notifications.group]
78
[clojars.notifications.user]
89
[clojars.test-helper :as help :refer [with-test-system]]
10+
[clojure.string :as str]
911
[clojure.test :refer [deftest is testing use-fixtures]]
1012
[kerodon.core :refer [check fill-in follow follow-redirect
1113
press session visit within]]
@@ -278,72 +280,107 @@
278280
(has (text? "The reset code was not found. Please ask for a new code in the forgot password page")))))
279281

280282
(deftest admin-can-add-member-to-group
281-
(-> (session (help/app))
282-
(register-as "fixture" "fixture@example.org" "password"))
283-
(-> (session (help/app))
284-
(register-as "dantheman" "test@example.org" "password")
285-
(visit "/groups/org.clojars.dantheman")
286-
(fill-in [:#username] "fixture")
287-
(press "Add Member")
288-
;;(follow-redirect)
289-
(within [:table.group-member-list
290-
[:tr enlive/last-of-type]
291-
[:td enlive/first-of-type]]
292-
(has (text? "fixture")))
293-
(within [:table.group-member-list
294-
[:tr enlive/last-of-type]
295-
[:td (enlive/nth-of-type 2)]]
296-
(has (text? "No"))))
297-
298-
(is (some #{"fixture"} (db/group-membernames help/*db* "org.clojars.dantheman")))
299-
300-
(help/match-audit {:username "dantheman"}
301-
{:tag "member-added"
302-
:user "dantheman"
303-
:group_name "org.clojars.dantheman"
304-
:message "user 'fixture' added as member"}))
283+
(with-test-system
284+
(-> (session (help/app))
285+
(register-as "fixture" "fixture@example.org" "password"))
286+
(-> (session (help/app))
287+
(register-as "dantheman" "test@example.org" "password")
288+
((fn [session] (email/expect-mock-emails 2) session))
289+
(visit "/groups/org.clojars.dantheman")
290+
(fill-in [:#username] "fixture")
291+
(press "Add Member")
292+
;;(follow-redirect)
293+
(within [:table.group-member-list
294+
[:tr enlive/last-of-type]
295+
[:td enlive/first-of-type]]
296+
(has (text? "fixture")))
297+
(within [:table.group-member-list
298+
[:tr enlive/last-of-type]
299+
[:td (enlive/nth-of-type 2)]]
300+
(has (text? "No"))))
301+
302+
(is (some #{"fixture"} (db/group-membernames help/*db* "org.clojars.dantheman")))
303+
304+
(help/match-audit {:username "dantheman"}
305+
{:tag "member-added"
306+
:user "dantheman"
307+
:group_name "org.clojars.dantheman"
308+
:message "user 'fixture' added as member"})
309+
310+
(is (true? (email/wait-for-mock-emails)))
311+
(is (= 2 (count @email/mock-emails)))
312+
(is (= #{"fixture@example.org" "test@example.org"}
313+
(into #{} (map first) @email/mock-emails)))
314+
(is (every? #(= "A member was added to the group org.clojars.dantheman"
315+
%)
316+
(into [] (map second) @email/mock-emails)))
317+
(is (every? #(str/starts-with? % "User 'fixture' was added to the org.clojars.dantheman group by dantheman.\n\n")
318+
(into [] (map #(nth % 2)) @email/mock-emails)))))
305319

306320
(deftest admin-can-add-admin-to-group
307-
(-> (session (help/app))
308-
(register-as "fixture" "fixture@example.org" "password"))
309-
(-> (session (help/app))
310-
(register-as "dantheman" "test@example.org" "password")
311-
(visit "/groups/org.clojars.dantheman")
312-
(fill-in [:#username] "fixture")
313-
(check [:#admin])
314-
(press "Add Member")
315-
;;(follow-redirect)
316-
(within [:table.group-member-list
317-
[:tr enlive/last-of-type]
318-
[:td enlive/first-of-type]]
319-
(has (text? "fixture")))
320-
(within [:table.group-member-list
321-
[:tr enlive/last-of-type]
322-
[:td (enlive/nth-of-type 2)]]
323-
(has (text? "Yes"))))
324-
325-
(is (some #{"fixture"} (db/group-adminnames help/*db* "org.clojars.dantheman")))
326-
327-
(help/match-audit {:username "dantheman"}
328-
{:tag "member-added"
329-
:user "dantheman"
330-
:group_name "org.clojars.dantheman"
331-
:message "user 'fixture' added as admin"}))
321+
(with-test-system
322+
(-> (session (help/app))
323+
(register-as "fixture" "fixture@example.org" "password"))
324+
(-> (session (help/app))
325+
(register-as "dantheman" "test@example.org" "password")
326+
((fn [session] (email/expect-mock-emails 2) session))
327+
(visit "/groups/org.clojars.dantheman")
328+
(fill-in [:#username] "fixture")
329+
(check [:#admin])
330+
(press "Add Member")
331+
(within [:table.group-member-list
332+
[:tr enlive/last-of-type]
333+
[:td enlive/first-of-type]]
334+
(has (text? "fixture")))
335+
(within [:table.group-member-list
336+
[:tr enlive/last-of-type]
337+
[:td (enlive/nth-of-type 2)]]
338+
(has (text? "Yes"))))
339+
340+
(is (some #{"fixture"} (db/group-adminnames help/*db* "org.clojars.dantheman")))
341+
342+
(help/match-audit {:username "dantheman"}
343+
{:tag "member-added"
344+
:user "dantheman"
345+
:group_name "org.clojars.dantheman"
346+
:message "user 'fixture' added as admin"})
347+
348+
(is (true? (email/wait-for-mock-emails)))
349+
(is (= 2 (count @email/mock-emails)))
350+
(is (= #{"fixture@example.org" "test@example.org"}
351+
(into #{} (map first) @email/mock-emails)))
352+
(is (every? #(= "An admin member was added to the group org.clojars.dantheman"
353+
%)
354+
(into [] (map second) @email/mock-emails)))
355+
(is (every? #(str/starts-with? % "User 'fixture' was added as an admin to the org.clojars.dantheman group by dantheman.\n\n")
356+
(into [] (map #(nth % 2)) @email/mock-emails)))))
332357

333358
(deftest admin-can-remove-user-from-group
334-
(-> (session (help/app))
335-
(register-as "fixture" "fixture@example.org" "password"))
336-
(-> (session (help/app))
337-
(register-as "dantheman" "test@example.org" "password")
338-
(visit "/groups/org.clojars.dantheman")
339-
(fill-in [:#username] "fixture")
340-
(press "Add Member")
341-
(press "Remove Member"))
342-
(help/match-audit {:username "dantheman"}
343-
{:tag "member-removed"
344-
:user "dantheman"
345-
:group_name "org.clojars.dantheman"
346-
:message "user 'fixture' removed"}))
359+
(with-test-system
360+
(-> (session (help/app))
361+
(register-as "fixture" "fixture@example.org" "password"))
362+
(-> (session (help/app))
363+
(register-as "dantheman" "test@example.org" "password")
364+
(visit "/groups/org.clojars.dantheman")
365+
(fill-in [:#username] "fixture")
366+
(press "Add Member")
367+
((fn [session] (email/expect-mock-emails 2) session))
368+
(press "Remove Member"))
369+
(help/match-audit {:username "dantheman"}
370+
{:tag "member-removed"
371+
:user "dantheman"
372+
:group_name "org.clojars.dantheman"
373+
:message "user 'fixture' removed"})
374+
375+
(is (true? (email/wait-for-mock-emails)))
376+
(is (= 2 (count @email/mock-emails)))
377+
(is (= #{"fixture@example.org" "test@example.org"}
378+
(into #{} (map first) @email/mock-emails)))
379+
(is (every? #(= "A member was removed from the group org.clojars.dantheman"
380+
%)
381+
(into [] (map second) @email/mock-emails)))
382+
(is (every? #(str/starts-with? % "User 'fixture' was removed from the org.clojars.dantheman group by dantheman.\n\n")
383+
(into [] (map #(nth % 2)) @email/mock-emails)))))
347384

348385
(deftest user-must-exist-to-be-added-to-group
349386
(-> (session (help/app))

0 commit comments

Comments
 (0)