Game sync and live services for independent game developers (targeting itch.io)
1use anyhow::Result;
2use clap::{Parser, Subcommand};
3use std::path::PathBuf;
4
5mod db;
6mod invite;
7mod quota;
8mod stats;
9
10#[derive(Parser)]
11#[command(name = "scratchback-admin")]
12#[command(about = "Scratchback Admin CLI")]
13struct Cli {
14 #[arg(short, long, default_value = "/var/data/scratchback/scratchback.db")]
15 database: PathBuf,
16
17 #[command(subcommand)]
18 command: Commands,
19}
20
21#[derive(Subcommand)]
22enum Commands {
23 /// Generate invite codes
24 Invite {
25 #[command(subcommand)]
26 subcommand: InviteCommands,
27 },
28 /// Show statistics
29 Stats,
30 /// Run database migrations
31 Migrate,
32 /// Manage gamer quotas
33 Quota {
34 #[command(subcommand)]
35 subcommand: QuotaCommands,
36 },
37}
38
39#[derive(Subcommand)]
40enum InviteCommands {
41 /// Generate new invite codes
42 Generate {
43 /// Number of codes to generate
44 #[arg(short, long, default_value = "1")]
45 count: usize,
46
47 /// Days until expiration (default: 30)
48 #[arg(short, long, default_value = "30")]
49 expires: u32,
50 },
51 /// List all invite codes
52 List {
53 /// Show only unused codes
54 #[arg(short, long)]
55 unused: bool,
56 },
57}
58
59#[derive(Subcommand)]
60enum QuotaCommands {
61 /// List all gamer quotas
62 List,
63 /// Set custom quota limit for a gamer
64 Set {
65 /// Gamer ID
66 gamer_id: String,
67 /// New limit in bytes
68 limit_bytes: u64,
69 },
70 /// Reset a gamer's used bytes to 0 and warning_sent to FALSE
71 Reset {
72 /// Gamer ID
73 gamer_id: String,
74 },
75 /// Show quota details for a specific gamer
76 Show {
77 /// Gamer ID
78 gamer_id: String,
79 },
80}
81
82#[tokio::main]
83async fn main() -> Result<()> {
84 tracing_subscriber::fmt::init();
85
86 let cli = Cli::parse();
87
88 match cli.command {
89 Commands::Invite { subcommand } => match subcommand {
90 InviteCommands::Generate { count, expires } => {
91 invite::generate(count, expires, &cli.database).await?
92 }
93 InviteCommands::List { unused } => invite::list(&cli.database, unused).await?,
94 },
95 Commands::Stats => stats::show(&cli.database).await?,
96 Commands::Migrate => db::migrate(&cli.database).await?,
97 Commands::Quota { subcommand } => match subcommand {
98 QuotaCommands::List => quota::list(&cli.database).await?,
99 QuotaCommands::Set { gamer_id, limit_bytes } => {
100 quota::set(&cli.database, &gamer_id, limit_bytes as i64).await?
101 }
102 QuotaCommands::Reset { gamer_id } => quota::reset(&cli.database, &gamer_id).await?,
103 QuotaCommands::Show { gamer_id } => quota::show(&cli.database, &gamer_id).await?,
104 },
105 }
106
107 Ok(())
108}