1
- use crate :: schema:: users;
1
+ use crate :: email:: Email ;
2
+ use crate :: schema:: { emails, users} ;
2
3
use crate :: tasks:: spawn_blocking;
3
4
use crate :: worker:: Environment ;
4
5
use crates_io_worker:: BackgroundJob ;
5
6
use diesel:: prelude:: * ;
6
7
use diesel:: RunQueryDsl ;
7
8
use std:: collections:: HashSet ;
9
+ use std:: fmt:: { Display , Formatter } ;
8
10
use std:: sync:: Arc ;
9
11
10
12
/// See <https://github.com/rust-lang/team/blob/master/teams/crates-io-admins.toml>.
@@ -31,57 +33,83 @@ impl BackgroundJob for SyncAdmins {
31
33
let mut conn = ctx. connection_pool . get ( ) ?;
32
34
33
35
let database_admins = users:: table
34
- . select ( ( users:: gh_id, users:: gh_login) )
36
+ . left_join ( emails:: table)
37
+ . select ( ( users:: gh_id, users:: gh_login, emails:: email. nullable ( ) ) )
35
38
. filter ( users:: is_admin. eq ( true ) )
36
- . get_results :: < ( i32 , String ) > ( & mut conn) ?;
39
+ . get_results :: < ( i32 , String , Option < String > ) > ( & mut conn) ?;
37
40
38
41
let database_admin_ids = database_admins
39
42
. iter ( )
40
- . map ( |( gh_id, _) | * gh_id)
43
+ . map ( |( gh_id, _, _ ) | * gh_id)
41
44
. collect :: < HashSet < _ > > ( ) ;
42
45
43
46
let new_admin_ids = repo_admin_ids
44
47
. difference ( & database_admin_ids)
45
48
. collect :: < HashSet < _ > > ( ) ;
46
49
47
- if new_admin_ids. is_empty ( ) {
50
+ let new_admins = if new_admin_ids. is_empty ( ) {
48
51
debug ! ( "No new admins to add" ) ;
52
+ vec ! [ ]
49
53
} else {
50
54
let new_admins = repo_admins
51
55
. iter ( )
52
56
. filter ( |m| new_admin_ids. contains ( & & m. github_id ) )
53
57
. map ( |m| format ! ( "{} (github_id: {})" , m. github, m. github_id) )
54
- . collect :: < Vec < _ > > ( )
55
- . join ( ", " ) ;
58
+ . collect :: < Vec < _ > > ( ) ;
56
59
57
- info ! ( "Adding new admins: {}" , new_admins) ;
60
+ info ! ( "Adding new admins: {}" , new_admins. join ( ", " ) ) ;
58
61
59
62
diesel:: update ( users:: table)
60
63
. filter ( users:: gh_id. eq_any ( new_admin_ids) )
61
64
. set ( users:: is_admin. eq ( true ) )
62
65
. execute ( & mut conn) ?;
63
- }
66
+
67
+ new_admins
68
+ } ;
64
69
65
70
let obsolete_admin_ids = database_admin_ids
66
71
. difference ( & repo_admin_ids)
67
72
. collect :: < HashSet < _ > > ( ) ;
68
73
69
- if obsolete_admin_ids. is_empty ( ) {
74
+ let obsolete_admins = if obsolete_admin_ids. is_empty ( ) {
70
75
debug ! ( "No obsolete admins to remove" ) ;
76
+ vec ! [ ]
71
77
} else {
72
78
let obsolete_admins = database_admins
73
79
. iter ( )
74
- . filter ( |( gh_id, _) | obsolete_admin_ids. contains ( & gh_id) )
75
- . map ( |( gh_id, login) | format ! ( "{} (github_id: {})" , login, gh_id) )
76
- . collect :: < Vec < _ > > ( )
77
- . join ( ", " ) ;
80
+ . filter ( |( gh_id, _, _) | obsolete_admin_ids. contains ( & gh_id) )
81
+ . map ( |( gh_id, login, _) | format ! ( "{} (github_id: {})" , login, gh_id) )
82
+ . collect :: < Vec < _ > > ( ) ;
78
83
79
- info ! ( "Removing obsolete admins: {}" , obsolete_admins) ;
84
+ info ! ( "Removing obsolete admins: {}" , obsolete_admins. join ( ", " ) ) ;
80
85
81
86
diesel:: update ( users:: table)
82
87
. filter ( users:: gh_id. eq_any ( obsolete_admin_ids) )
83
88
. set ( users:: is_admin. eq ( false ) )
84
89
. execute ( & mut conn) ?;
90
+
91
+ obsolete_admins
92
+ } ;
93
+
94
+ if !new_admins. is_empty ( ) || !obsolete_admins. is_empty ( ) {
95
+ let email = AdminAccountEmail :: new ( new_admins, obsolete_admins) ;
96
+
97
+ for database_admin in & database_admins {
98
+ let ( _, _, email_address) = database_admin;
99
+ if let Some ( email_address) = email_address {
100
+ if let Err ( error) = ctx. emails . send ( email_address, email. clone ( ) ) {
101
+ warn ! (
102
+ "Failed to send email to admin {} ({}, github_id: {}): {}" ,
103
+ database_admin. 1 , email_address, database_admin. 0 , error
104
+ ) ;
105
+ }
106
+ } else {
107
+ warn ! (
108
+ "No email address found for admin {} (github_id: {})" ,
109
+ database_admin. 1 , database_admin. 0
110
+ ) ;
111
+ }
112
+ }
85
113
}
86
114
87
115
Ok ( ( ) )
@@ -91,3 +119,47 @@ impl BackgroundJob for SyncAdmins {
91
119
Ok ( ( ) )
92
120
}
93
121
}
122
+
123
+ #[ derive( Debug , Clone ) ]
124
+ struct AdminAccountEmail {
125
+ new_admins : Vec < String > ,
126
+ obsolete_admins : Vec < String > ,
127
+ }
128
+
129
+ impl AdminAccountEmail {
130
+ fn new ( new_admins : Vec < String > , obsolete_admins : Vec < String > ) -> Self {
131
+ Self {
132
+ new_admins,
133
+ obsolete_admins,
134
+ }
135
+ }
136
+ }
137
+
138
+ impl Email for AdminAccountEmail {
139
+ const SUBJECT : & ' static str = "crates.io: Admin account changes" ;
140
+
141
+ fn body ( & self ) -> String {
142
+ self . to_string ( )
143
+ }
144
+ }
145
+
146
+ impl Display for AdminAccountEmail {
147
+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> std:: fmt:: Result {
148
+ if !self . new_admins . is_empty ( ) {
149
+ writeln ! ( f, "New admins have been added:\n " ) ?;
150
+ for new_admin in & self . new_admins {
151
+ writeln ! ( f, "- {}" , new_admin) ?;
152
+ }
153
+ writeln ! ( f) ?;
154
+ }
155
+
156
+ if !self . obsolete_admins . is_empty ( ) {
157
+ writeln ! ( f, "Admin access has been revoked for:" ) ?;
158
+ for obsolete_admin in & self . obsolete_admins {
159
+ writeln ! ( f, "- {}" , obsolete_admin) ?;
160
+ }
161
+ }
162
+
163
+ Ok ( ( ) )
164
+ }
165
+ }
0 commit comments