11use crate :: dialoguer;
22use anyhow:: Context ;
3+ use chrono:: { NaiveDateTime , Utc } ;
34use colored:: Colorize ;
4- use crates_io:: schema:: crate_downloads;
5+ use crates_io:: models:: { NewDeletedCrate , User } ;
6+ use crates_io:: schema:: { crate_downloads, deleted_crates} ;
57use crates_io:: worker:: jobs;
68use crates_io:: { db, schema:: crates} ;
79use crates_io_worker:: BackgroundJob ;
810use diesel:: dsl:: sql;
911use diesel:: expression:: SqlLiteral ;
1012use diesel:: prelude:: * ;
1113use diesel:: sql_types:: { Array , BigInt , Text } ;
12- use diesel_async:: RunQueryDsl ;
14+ use diesel_async:: scoped_futures:: ScopedFutureExt ;
15+ use diesel_async:: { AsyncConnection , AsyncPgConnection , RunQueryDsl } ;
1316use std:: fmt:: Display ;
1417
1518#[ derive( clap:: Parser , Debug ) ]
@@ -26,6 +29,14 @@ pub struct Opts {
2629 /// Don't ask for confirmation: yes, we are sure. Best for scripting.
2730 #[ arg( short, long) ]
2831 yes : bool ,
32+
33+ /// Your GitHub username.
34+ #[ arg( long) ]
35+ deleted_by : String ,
36+
37+ /// An optional message explaining why the crate was deleted.
38+ #[ arg( long) ]
39+ message : Option < String > ,
2940}
3041
3142pub async fn run ( opts : Opts ) -> anyhow:: Result < ( ) > {
@@ -44,6 +55,10 @@ pub async fn run(opts: Opts) -> anyhow::Result<()> {
4455 . await
4556 . context ( "Failed to look up crate name from the database" ) ?;
4657
58+ let deleted_by = User :: async_find_by_login ( & mut conn, & opts. deleted_by )
59+ . await
60+ . context ( "Failed to look up `--deleted-by` user from the database" ) ?;
61+
4762 println ! ( "Deleting the following crates:" ) ;
4863 println ! ( ) ;
4964 for name in & crate_names {
@@ -58,17 +73,29 @@ pub async fn run(opts: Opts) -> anyhow::Result<()> {
5873 return Ok ( ( ) ) ;
5974 }
6075
76+ let now = Utc :: now ( ) ;
77+
6178 for name in & crate_names {
6279 if let Some ( crate_info) = existing_crates. iter ( ) . find ( |info| info. name == * name) {
6380 let id = crate_info. id ;
6481
82+ let created_at = crate_info. created_at . and_utc ( ) ;
83+ let deleted_crate = NewDeletedCrate :: builder ( name)
84+ . created_at ( & created_at)
85+ . deleted_at ( & now)
86+ . deleted_by ( deleted_by. id )
87+ . maybe_message ( opts. message . as_deref ( ) )
88+ . available_at ( & now)
89+ . build ( ) ;
90+
6591 info ! ( "{name}: Deleting crate from the database…" ) ;
66- if let Err ( error) = diesel:: delete ( crates:: table. find ( id) )
67- . execute ( & mut conn)
68- . await
69- {
92+ let result = conn
93+ . transaction ( |conn| delete_from_database ( conn, id, deleted_crate) . scope_boxed ( ) )
94+ . await ;
95+
96+ if let Err ( error) = result {
7097 warn ! ( %id, "{name}: Failed to delete crate from the database: {error}" ) ;
71- }
98+ } ;
7299 } else {
73100 info ! ( "{name}: Skipped missing crate" ) ;
74101 } ;
@@ -94,12 +121,31 @@ pub async fn run(opts: Opts) -> anyhow::Result<()> {
94121 Ok ( ( ) )
95122}
96123
124+ async fn delete_from_database (
125+ conn : & mut AsyncPgConnection ,
126+ crate_id : i32 ,
127+ deleted_crate : NewDeletedCrate < ' _ > ,
128+ ) -> anyhow:: Result < ( ) > {
129+ diesel:: delete ( crates:: table. find ( crate_id) )
130+ . execute ( conn)
131+ . await ?;
132+
133+ diesel:: insert_into ( deleted_crates:: table)
134+ . values ( deleted_crate)
135+ . execute ( conn)
136+ . await ?;
137+
138+ Ok ( ( ) )
139+ }
140+
97141#[ derive( Debug , Clone , Queryable , Selectable ) ]
98142struct CrateInfo {
99143 #[ diesel( select_expression = crates:: columns:: name) ]
100144 name : String ,
101145 #[ diesel( select_expression = crates:: columns:: id) ]
102146 id : i32 ,
147+ #[ diesel( select_expression = crates:: columns:: created_at) ]
148+ created_at : NaiveDateTime ,
103149 #[ diesel( select_expression = crate_downloads:: columns:: downloads) ]
104150 downloads : i64 ,
105151 #[ diesel( select_expression = owners_subquery( ) ) ]
0 commit comments