import argparse import sys from devplacepy.database import get_table from devplacepy.utils import strip_html def cmd_role_get(args): users = get_table("users") user = users.find_one(username=args.username) if not user: print(f"User '{args.username}' not found") sys.exit(1) print(user.get("role", "member").lower()) def cmd_role_set(args): role = args.role.lower() if role not in ("member", "admin"): print("Role must be 'member' or 'admin'") sys.exit(1) users = get_table("users") user = users.find_one(username=args.username) if not user: print(f"User '{args.username}' not found") sys.exit(1) users.update({"uid": user["uid"], "role": role.capitalize()}, ["uid"]) print(f"User '{args.username}' role set to '{role}'") def cmd_news_clear(args): from devplacepy.database import db for table in ("news", "news_images", "news_sync"): if table in db.tables: count = len(list(db[table].all())) db[table].delete() print(f"Deleted {count} rows from '{table}'") else: print(f"Table '{table}' does not exist, skipping") print("News data cleared") def cmd_news_sanitize(args): from devplacepy.database import db if "news" not in db.tables: print("News table does not exist") return news_table = db["news"] updated = 0 for row in news_table.all(): desc = (strip_html(row.get("description", "") or ""))[:5000] content = (strip_html(row.get("content", "") or ""))[:10000] if desc != row.get("description", "") or content != row.get("content", ""): news_table.update({"id": row["id"], "description": desc, "content": content}, ["id"]) updated += 1 print(f"Sanitized {updated} news article(s)") def cmd_attachments_prune(args): from devplacepy.database import db from devplacepy.config import STATIC_DIR import os deleted_records = 0 deleted_files = 0 freed_bytes = 0 if "attachments" in db.tables: orphans = list(db["attachments"].find(resource_uid="")) orphans += list(db["attachments"].find(resource_type="")) seen = set() unique_orphans = [] for o in orphans: if o["uid"] not in seen: seen.add(o["uid"]) unique_orphans.append(o) for att in unique_orphans: sp = att.get("storage_path", "") if sp: fp = STATIC_DIR / "uploads" / sp try: if fp.exists(): freed_bytes += fp.stat().st_size fp.unlink() deleted_files += 1 parent = fp.parent if parent.exists() and not any(parent.iterdir()): parent.rmdir() grandparent = parent.parent if grandparent.exists() and not any(grandparent.iterdir()): grandparent.rmdir() except Exception as e: print(f" Error deleting {sp}: {e}") db["attachments"].delete(id=att["id"]) deleted_records += 1 print(f"Pruned {deleted_records} orphan records, {deleted_files} files, {freed_bytes / 1024:.1f} KB freed") def main(): parser = argparse.ArgumentParser(description="DevPlace admin CLI") sub = parser.add_subparsers(title="commands", dest="command") role = sub.add_parser("role", help="Manage user roles") role_sub = role.add_subparsers(title="action", dest="action") role_get = role_sub.add_parser("get", help="Get a user's role") role_get.add_argument("username") role_get.set_defaults(func=cmd_role_get) role_set = role_sub.add_parser("set", help="Set a user's role") role_set.add_argument("username") role_set.add_argument("role", choices=["member", "admin"]) role_set.set_defaults(func=cmd_role_set) news = sub.add_parser("news", help="News management") news_sub = news.add_subparsers(title="action", dest="action") news_clear = news_sub.add_parser("clear", help="Delete all news from local database") news_clear.set_defaults(func=cmd_news_clear) news_sanitize = news_sub.add_parser("sanitize", help="Strip HTML from all existing news descriptions and content") news_sanitize.set_defaults(func=cmd_news_sanitize) attachments = sub.add_parser("attachments", help="Attachment management") att_sub = attachments.add_subparsers(title="action", dest="action") att_prune = att_sub.add_parser("prune", help="Remove orphaned attachment records and files") att_prune.set_defaults(func=cmd_attachments_prune) args = parser.parse_args() if hasattr(args, "func"): args.func(args) else: parser.print_help() sys.exit(1) if __name__ == "__main__": main()