@@ -39,6 +39,26 @@ pub enum AdminAction {
3939 #[ arg( long) ]
4040 force : bool ,
4141 } ,
42+ /// Generate invite codes for leaderboard(s)
43+ GenerateInvites {
44+ /// Leaderboard names to grant access to
45+ #[ arg( long, required = true , num_args = 1 ..) ]
46+ leaderboards : Vec < String > ,
47+
48+ /// Number of invite codes to generate (1-10000)
49+ #[ arg( long, default_value = "1" ) ]
50+ count : u32 ,
51+ } ,
52+ /// List invite codes for a leaderboard
53+ ListInvites {
54+ /// Leaderboard name
55+ leaderboard : String ,
56+ } ,
57+ /// Revoke an invite code
58+ RevokeInvite {
59+ /// The invite code to revoke
60+ code : String ,
61+ } ,
4262 /// Update problems from a GitHub repository (mirrors Discord /admin update-problems)
4363 UpdateProblems {
4464 /// Problem set name (e.g., "nvidia", "pmpp_v2"). If not specified, updates all.
@@ -56,6 +76,10 @@ pub enum AdminAction {
5676 /// Force update even if task definition changed significantly
5777 #[ arg( long) ]
5878 force : bool ,
79+
80+ /// Set leaderboard visibility to closed (requires invite to access)
81+ #[ arg( long) ]
82+ closed : bool ,
5983 } ,
6084}
6185
@@ -108,27 +132,109 @@ pub async fn handle_admin(action: AdminAction) -> Result<()> {
108132 println ! ( "Deleted leaderboard '{}'" , name) ;
109133 println ! ( "{}" , serde_json:: to_string_pretty( & result) ?) ;
110134 }
135+ AdminAction :: GenerateInvites {
136+ leaderboards,
137+ count,
138+ } => {
139+ let result = service:: admin_generate_invites ( & client, & leaderboards, count) . await ?;
140+ let codes = result[ "codes" ] . as_array ( ) . map ( |arr| arr. len ( ) ) . unwrap_or ( 0 ) ;
141+ let lbs = result[ "leaderboards" ]
142+ . as_array ( )
143+ . map ( |arr| {
144+ arr. iter ( )
145+ . filter_map ( |v| v. as_str ( ) )
146+ . collect :: < Vec < _ > > ( )
147+ . join ( ", " )
148+ } )
149+ . unwrap_or_default ( ) ;
150+ println ! ( "Generated {} invite code(s) for: {}" , codes, lbs) ;
151+ if let Some ( arr) = result[ "codes" ] . as_array ( ) {
152+ for code in arr {
153+ println ! ( " {}" , code. as_str( ) . unwrap_or( "???" ) ) ;
154+ }
155+ }
156+ }
157+ AdminAction :: ListInvites { leaderboard } => {
158+ let result = service:: admin_list_invites ( & client, & leaderboard) . await ?;
159+ let invites = result[ "invites" ] . as_array ( ) ;
160+ match invites {
161+ Some ( arr) if arr. is_empty ( ) => {
162+ println ! ( "No invites for '{}'" , leaderboard) ;
163+ }
164+ Some ( arr) => {
165+ let claimed = arr
166+ . iter ( )
167+ . filter ( |i| i[ "user_id" ] . as_str ( ) . is_some ( ) )
168+ . count ( ) ;
169+ println ! (
170+ "Invites for '{}': {} total, {} claimed, {} unclaimed\n " ,
171+ leaderboard,
172+ arr. len( ) ,
173+ claimed,
174+ arr. len( ) - claimed,
175+ ) ;
176+ let header = format ! (
177+ "{:<26} {:<16} {:<20} {}" ,
178+ "CODE" , "STATUS" , "CLAIMED BY" , "CREATED"
179+ ) ;
180+ println ! ( "{header}" ) ;
181+ println ! ( "{}" , "-" . repeat( 82 ) ) ;
182+ for invite in arr {
183+ let code = invite[ "code" ] . as_str ( ) . unwrap_or ( "???" ) ;
184+ let user = invite[ "user_name" ]
185+ . as_str ( )
186+ . or_else ( || invite[ "user_id" ] . as_str ( ) ) ;
187+ let status = if user. is_some ( ) {
188+ "claimed"
189+ } else {
190+ "unclaimed"
191+ } ;
192+ let user_display = user. unwrap_or ( "-" ) ;
193+ let created = invite[ "created_at" ] . as_str ( ) . unwrap_or ( "-" ) ;
194+ println ! (
195+ "{:<26} {:<16} {:<20} {}" ,
196+ code, status, user_display, created,
197+ ) ;
198+ }
199+ }
200+ None => {
201+ println ! ( "{}" , serde_json:: to_string_pretty( & result) ?) ;
202+ }
203+ }
204+ }
205+ AdminAction :: RevokeInvite { code } => {
206+ let result = service:: admin_revoke_invite ( & client, & code) . await ?;
207+ let was_claimed = result[ "was_claimed" ] . as_bool ( ) . unwrap_or ( false ) ;
208+ println ! (
209+ "Revoked invite code '{}' (was {})" ,
210+ code,
211+ if was_claimed { "claimed" } else { "unclaimed" }
212+ ) ;
213+ }
111214 AdminAction :: UpdateProblems {
112215 problem_set,
113216 repository,
114217 branch,
115218 force,
219+ closed,
116220 } => {
117221 println ! (
118- "Updating problems from {}/tree/{}{}..." ,
222+ "Updating problems from {}/tree/{}{}{} ..." ,
119223 repository,
120224 branch,
121225 problem_set
122226 . as_ref( )
123227 . map( |ps| format!( " (problem set: {})" , ps) )
124- . unwrap_or_default( )
228+ . unwrap_or_default( ) ,
229+ if closed { " (closed)" } else { "" } ,
125230 ) ;
126231 let result = service:: admin_update_problems (
127232 & client,
128233 problem_set. as_deref ( ) ,
129234 & repository,
130235 & branch,
131236 force,
237+ closed,
132238 )
133239 . await ?;
134240
0 commit comments