/* * voicecat-admin — offline account management tool. * * Directly manipulates {data_dir}/voicecat.db without a running server. * Usage: * voicecat-admin [--data-dir ] account add [--admin] [--password

] * voicecat-admin [--data-dir ] account reset [--password

] * voicecat-admin [--data-dir ] account del * voicecat-admin [--data-dir ] account list */ #include #include #include #include #include #include "db.h" using namespace voicecat::server; static void print_usage(const char* argv0) { std::fprintf(stderr, "Usage:\n" " %s [--data-dir ] account add [--admin] [--password

]\n" " %s [--data-dir ] account reset [--password

]\n" " %s [--data-dir ] account del \n" " %s [--data-dir ] account list\n", argv0, argv0, argv0, argv0); } static std::string read_password_stdin(const char* prompt) { std::fprintf(stderr, "%s: ", prompt); std::fflush(stderr); std::string pw; std::getline(std::cin, pw); return pw; } int main(int argc, char** argv) { std::string data_dir = "voicecat-data"; int i = 1; if (i < argc && std::strcmp(argv[i], "--data-dir") == 0) { if (++i >= argc) { std::fprintf(stderr, "Missing argument to --data-dir\n"); return 1; } data_dir = argv[i++]; } if (i >= argc || std::strcmp(argv[i], "account") != 0) { print_usage(argv[0]); return 1; } ++i; if (i >= argc) { print_usage(argv[0]); return 1; } std::string subcmd = argv[i++]; std::string db_path = data_dir + "/voicecat.db"; Database db(db_path); std::string error; if (!db.open(error)) { std::fprintf(stderr, "Failed to open database %s: %s\n", db_path.c_str(), error.c_str()); return 1; } if (subcmd == "list") { auto accounts = db.list_accounts(); if (accounts.empty()) { std::printf("(no accounts)\n"); } else { std::printf("%-20s %-5s %s\n", "username", "admin", "created_at"); std::printf("%-20s %-5s %s\n", "--------", "-----", "----------"); for (auto& acc : accounts) { std::printf("%-20s %-5s %lld\n", acc.username.c_str(), acc.is_admin ? "yes" : "no", static_cast(acc.created_at)); } } return 0; } if (subcmd == "add") { if (i >= argc) { std::fprintf(stderr, "add requires a username\n"); return 1; } std::string username = argv[i++]; bool is_admin = false; std::optional password; while (i < argc) { if (std::strcmp(argv[i], "--admin") == 0) { is_admin = true; ++i; } else if (std::strcmp(argv[i], "--password") == 0) { if (++i >= argc) { std::fprintf(stderr, "Missing argument to --password\n"); return 1; } password = argv[i++]; } else { std::fprintf(stderr, "Unknown option: %s\n", argv[i]); return 1; } } if (!password) password = read_password_stdin("Password"); if (password->empty()) { std::fprintf(stderr, "Password must not be empty\n"); return 1; } auto acc = db.create_account(username, *password, is_admin, error); if (!acc) { std::fprintf(stderr, "Failed to create account: %s\n", error.c_str()); return 1; } std::printf("Created account '%s'%s (id=%lld)\n", acc->username.c_str(), is_admin ? " [admin]" : "", static_cast(acc->id)); return 0; } if (subcmd == "reset") { if (i >= argc) { std::fprintf(stderr, "reset requires a username\n"); return 1; } std::string username = argv[i++]; std::optional password; while (i < argc) { if (std::strcmp(argv[i], "--password") == 0) { if (++i >= argc) { std::fprintf(stderr, "Missing argument to --password\n"); return 1; } password = argv[i++]; } else { std::fprintf(stderr, "Unknown option: %s\n", argv[i]); return 1; } } if (!password) password = read_password_stdin("New password"); if (password->empty()) { std::fprintf(stderr, "Password must not be empty\n"); return 1; } if (!db.reset_password(username, *password, error)) { std::fprintf(stderr, "Failed: %s\n", error.c_str()); return 1; } std::printf("Password reset for '%s'\n", username.c_str()); return 0; } if (subcmd == "del") { if (i >= argc) { std::fprintf(stderr, "del requires a username\n"); return 1; } std::string username = argv[i++]; if (!db.delete_account(username, error)) { std::fprintf(stderr, "Failed: %s\n", error.c_str()); return 1; } std::printf("Deleted account '%s'\n", username.c_str()); return 0; } std::fprintf(stderr, "Unknown subcommand: %s\n", subcmd.c_str()); print_usage(argv[0]); return 1; }