Add admin commands for database operations

Features:
- Add admin command group for database operations
- admin list-users: List all users from SQLite/PostgreSQL/MySQL
- admin user-notes: List notes for specific user
- Add CodiMDDatabase class for direct DB access
This commit is contained in:
2026-04-07 12:22:24 +08:00
parent 8508268a58
commit b34c503826
4 changed files with 278 additions and 0 deletions

View File

@@ -122,6 +122,12 @@ def config():
pass
@cli.group()
def admin():
"""Admin operations (require database access)."""
pass
# Note commands
@note.command("list")
@click.option("--limit", type=int, help="Limit number of results")
@@ -599,6 +605,109 @@ def config_set(key, value):
click.echo(f"Set {key} = {value}")
# Admin commands
@admin.command("list-users")
@click.option("--db-type", type=click.Choice(["sqlite", "postgresql", "mysql"]), default="sqlite", help="Database type")
@click.option("--db-path", type=Path, help="SQLite database path")
@click.option("--db-host", help="Database host (for PostgreSQL/MySQL)")
@click.option("--db-name", help="Database name (for PostgreSQL/MySQL)")
@click.option("--db-user", help="Database user (for PostgreSQL/MySQL)")
@click.option("--db-password", help="Database password (for PostgreSQL/MySQL)")
@click.option("--db-port", type=int, default=5432, help="Database port (default: 5432 for PostgreSQL, 3306 for MySQL)")
@handle_error
def admin_list_users(db_type, db_path, db_host, db_name, db_user, db_password, db_port):
"""List all users in the CodiMD database."""
from cli_anything.codimd.core import CodiMDDatabase
if db_type == "sqlite":
if not db_path:
# Try default CodiMD SQLite path
default_paths = [
"./codimd/db.sqlite",
"./db.sqlite",
"/var/lib/codimd/db.sqlite"
]
for path in default_paths:
if Path(path).exists():
db_path = path
break
if not db_path:
raise ValueError(
"SQLite database path not found. "
"Please specify with --db-path or ensure db.sqlite is in current directory."
)
users = CodiMDDatabase.list_users_sqlite(str(db_path))
elif db_type == "postgresql":
if not all([db_host, db_name, db_user]):
raise ValueError("--db-host, --db-name, and --db-user are required for PostgreSQL")
if not db_password:
db_password = click.prompt("Database password", hide_input=True)
users = CodiMDDatabase.list_users_postgresql(db_host, db_name, db_user, db_password, db_port)
elif db_type == "mysql":
if not all([db_host, db_name, db_user]):
raise ValueError("--db-host, --db-name, and --db-user are required for MySQL")
if not db_password:
db_password = click.prompt("Database password", hide_input=True)
users = CodiMDDatabase.list_users_mysql(db_host, db_name, db_user, db_password, db_port)
if ctx["json_mode"]:
output_json(format_success({"users": users, "count": len(users)}))
else:
if not users:
click.echo("No users found.")
else:
click.echo(f"Total users: {len(users)}")
for u in users:
user_id = u.get("id", "N/A")[:8] + "..." if len(u.get("id", "")) > 8 else u.get("id", "N/A")
email = u.get("email") or "N/A"
# Get name from profile
profile = u.get("profile")
if profile:
if isinstance(profile, str):
try:
profile = json.loads(profile)
except:
profile = {}
name = profile.get("name") or profile.get("displayName") or "N/A"
else:
name = "N/A"
created = u.get("createdAt", "N/A")
click.echo(f" {user_id} | {email:30s} | {name}")
if created != "N/A":
click.echo(f" Created: {created}")
@admin.command("user-notes")
@click.argument("user_id")
@click.option("--db-path", type=Path, default="./db.sqlite", help="SQLite database path")
@handle_error
def admin_user_notes(user_id, db_path):
"""List all notes for a specific user."""
from cli_anything.codimd.core import CodiMDDatabase
if not Path(db_path).exists():
raise FileNotFoundError(f"Database not found: {db_path}")
notes = CodiMDDatabase.list_user_notes_sqlite(str(db_path), user_id)
if ctx["json_mode"]:
output_json(format_success({"user_id": user_id, "notes": notes, "count": len(notes)}))
else:
if not notes:
click.echo(f"No notes found for user {user_id}")
else:
click.echo(f"Notes for user {user_id}: {len(notes)} total")
for n in notes:
note_id = n.get("shortid", n.get("id", "N/A"))
title = n.get("title") or "Untitled"
permission = n.get("permission", "N/A")
click.echo(f" {note_id} | {title[:50]} | [{permission}]")
# REPL mode
@cli.command("repl")
@handle_error