1+ use  std:: io; 
2+ 
13use  crate :: common_args; 
24use  crate :: config:: Config ; 
3- use  crate :: util:: { add_auth_header_opt,  database_identity,  get_auth_header} ; 
5+ use  crate :: util:: { add_auth_header_opt,  database_identity,  get_auth_header,  y_or_n ,   AuthHeader } ; 
46use  clap:: { Arg ,  ArgMatches } ; 
7+ use  http:: StatusCode ; 
8+ use  itertools:: Itertools  as  _; 
9+ use  reqwest:: Response ; 
10+ use  spacetimedb_client_api_messages:: http:: { DatabaseDeleteConfirmationResponse ,  DatabaseTree ,  DatabaseTreeNode } ; 
11+ use  spacetimedb_lib:: Hash ; 
12+ use  tokio:: io:: AsyncWriteExt  as  _; 
513
614pub  fn  cli ( )  -> clap:: Command  { 
715    clap:: Command :: new ( "delete" ) 
@@ -22,11 +30,143 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E
2230    let  force = args. get_flag ( "force" ) ; 
2331
2432    let  identity = database_identity ( & config,  database,  server) . await ?; 
25- 
26-     let  builder  = reqwest :: Client :: new ( ) . delete ( format ! ( "{}/v1/database/{}"  ,  config . get_host_url ( server ) ? ,  identity ) ) ; 
33+      let  host_url = config . get_host_url ( server ) ? ; 
34+     let  request_path  = format ! ( "{host_url }/v1/database/{identity}"  ) ; 
2735    let  auth_header = get_auth_header ( & mut  config,  false ,  server,  !force) . await ?; 
28-     let  builder = add_auth_header_opt ( builder,  & auth_header) ; 
29-     builder. send ( ) . await ?. error_for_status ( ) ?; 
36+     let  client = reqwest:: Client :: new ( ) ; 
37+ 
38+     let  response = send_request ( & client,  & request_path,  & auth_header,  None ) . await ?; 
39+     match  response. status ( )  { 
40+         StatusCode :: PRECONDITION_REQUIRED  => { 
41+             let  confirm = response. json :: < DatabaseDeleteConfirmationResponse > ( ) . await ?; 
42+             println ! ( "WARNING: Deleting the database {identity} will also delete its children!" ) ; 
43+             if  !force { 
44+                 print_database_tree_info ( & confirm. database_tree ) . await ?; 
45+             } 
46+             if  y_or_n ( force,  "Do you want to proceed deleting above databases?" ) ? { 
47+                 send_request ( & client,  & request_path,  & auth_header,  Some ( confirm. confirmation_token ) ) 
48+                     . await ?
49+                     . error_for_status ( ) ?; 
50+             }  else  { 
51+                 println ! ( "Aborting" ) ; 
52+             } 
53+ 
54+             Ok ( ( ) ) 
55+         } 
56+         StatusCode :: OK  => Ok ( ( ) ) , 
57+         _ => response. error_for_status ( ) . map ( drop) . map_err ( Into :: into) , 
58+     } 
59+ } 
60+ 
61+ async  fn  send_request ( 
62+     client :  & reqwest:: Client , 
63+     request_path :  & str , 
64+     auth :  & AuthHeader , 
65+     confirmation_token :  Option < Hash > , 
66+ )  -> Result < Response ,  reqwest:: Error >  { 
67+     let  mut  builder = client. delete ( request_path) ; 
68+     builder = add_auth_header_opt ( builder,  auth) ; 
69+     if  let  Some ( token)  = confirmation_token { 
70+         builder = builder. query ( & [ ( "token" ,  token) ] ) ; 
71+     } 
72+     builder. send ( ) . await 
73+ } 
74+ 
75+ async  fn  print_database_tree_info ( tree :  & DatabaseTree )  -> io:: Result < ( ) >  { 
76+     tokio:: io:: stdout ( ) 
77+         . write_all ( as_termtree ( tree) . to_string ( ) . as_bytes ( ) ) 
78+         . await 
79+ } 
80+ 
81+ fn  as_termtree ( tree :  & DatabaseTree )  -> termtree:: Tree < String >  { 
82+     let  mut  stack:  Vec < ( & DatabaseTree ,  bool ) >  = vec ! [ ] ; 
83+     stack. push ( ( tree,  false ) ) ; 
84+ 
85+     let  mut  built:  Vec < termtree:: Tree < String > >  = <_ >:: default ( ) ; 
86+ 
87+     while  let  Some ( ( node,  visited) )  = stack. pop ( )  { 
88+         if  visited { 
89+             let  mut  term_node = termtree:: Tree :: new ( fmt_tree_node ( & node. root ) ) ; 
90+             term_node. leaves  = built. drain ( built. len ( )  - node. children . len ( ) ..) . collect ( ) ; 
91+             term_node. leaves . reverse ( ) ; 
92+             built. push ( term_node) ; 
93+         }  else  { 
94+             stack. push ( ( node,  true ) ) ; 
95+             stack. extend ( node. children . iter ( ) . rev ( ) . map ( |child| ( child,  false ) ) ) ; 
96+         } 
97+     } 
98+ 
99+     built
100+         . pop ( ) 
101+         . expect ( "database tree contains a root and we pushed it last" ) 
102+ } 
103+ 
104+ fn  fmt_tree_node ( node :  & DatabaseTreeNode )  -> String  { 
105+     format ! ( 
106+         "{}{}" , 
107+         node. database_identity, 
108+         if  node. database_names. is_empty( )  { 
109+             <_>:: default ( ) 
110+         }  else { 
111+             format!( ": {}" ,  node. database_names. iter( ) . join( ", " ) ) 
112+         } 
113+     ) 
114+ } 
115+ 
116+ #[ cfg( test) ]  
117+ mod  tests { 
118+     use  super :: * ; 
119+     use  spacetimedb_client_api_messages:: http:: { DatabaseTree ,  DatabaseTreeNode } ; 
120+     use  spacetimedb_lib:: { sats:: u256,  Identity } ; 
30121
31-     Ok ( ( ) ) 
122+     #[ test]  
123+     fn  render_termtree ( )  { 
124+         let  tree = DatabaseTree  { 
125+             root :  DatabaseTreeNode  { 
126+                 database_identity :  Identity :: ONE , 
127+                 database_names :  [ "parent" . into ( ) ] . into ( ) , 
128+             } , 
129+             children :  vec ! [ 
130+                 DatabaseTree  { 
131+                     root:  DatabaseTreeNode  { 
132+                         database_identity:  Identity :: from_u256( u256:: new( 2 ) ) , 
133+                         database_names:  [ "child" . into( ) ] . into( ) , 
134+                     } , 
135+                     children:  vec![ 
136+                         DatabaseTree  { 
137+                             root:  DatabaseTreeNode  { 
138+                                 database_identity:  Identity :: from_u256( u256:: new( 3 ) ) , 
139+                                 database_names:  [ "grandchild" . into( ) ] . into( ) , 
140+                             } , 
141+                             children:  vec![ ] , 
142+                         } , 
143+                         DatabaseTree  { 
144+                             root:  DatabaseTreeNode  { 
145+                                 database_identity:  Identity :: from_u256( u256:: new( 5 ) ) , 
146+                                 database_names:  [ ] . into( ) , 
147+                             } , 
148+                             children:  vec![ ] , 
149+                         } , 
150+                     ] , 
151+                 } , 
152+                 DatabaseTree  { 
153+                     root:  DatabaseTreeNode  { 
154+                         database_identity:  Identity :: from_u256( u256:: new( 4 ) ) , 
155+                         database_names:  [ "sibling" . into( ) ,  "bro" . into( ) ] . into( ) , 
156+                     } , 
157+                     children:  vec![ ] , 
158+                 } , 
159+             ] , 
160+         } ; 
161+         pretty_assertions:: assert_eq!( 
162+             "\  
163+ 
164+ ├── 0000000000000000000000000000000000000000000000000000000000000004: bro, sibling 
165+ └── 0000000000000000000000000000000000000000000000000000000000000002: child 
166+     ├── 0000000000000000000000000000000000000000000000000000000000000005 
167+     └── 0000000000000000000000000000000000000000000000000000000000000003: grandchild 
168+ " , 
169+             & as_termtree( & tree) . to_string( ) 
170+         ) ; 
171+     } 
32172} 
0 commit comments