| """ |
| RMI Admin Backend Router |
| ========================== |
| Complete admin panel API with RBAC, security, and management tools. |
| |
| All endpoints require X-Admin-Session header (JWT session token). |
| Some endpoints also require specific permissions based on role. |
| |
| Sections: |
| 1. Authentication (login, logout, session, 2FA) |
| 2. Dashboard (metrics, health, analytics) |
| 3. User Management (users, bans, roles, activity) |
| 4. Security (IP blocks, rate limits, audit logs, threats) |
| 5. System Management (config, services, health) |
| 6. Content Management (posts, announcements, SEO) |
| 7. Financial (x402 analytics, revenue, payments) |
| 8. API Keys (create, rotate, revoke, scopes) |
| 9. Token Deployer (darkroom integration) |
| 10. Backups & Maintenance |
| """ |
|
|
| import json |
| import logging |
| import os |
| import time |
| from datetime import datetime, timedelta |
|
|
| from fastapi import APIRouter, Body, HTTPException, Request |
| from pydantic import BaseModel, Field |
|
|
| from app.admin_backend import ( |
| AdminRole, |
| AdminUserStore, |
| AuditLogger, |
| SecurityManager, |
| SessionManager, |
| SystemHealthMonitor, |
| require_admin, |
| ) |
|
|
| logger = logging.getLogger("rmi_admin_router") |
|
|
| router = APIRouter(prefix="/api/v1/admin/backend", tags=["admin-backend"]) |
|
|
|
|
| |
|
|
|
|
| class AdminLoginRequest(BaseModel): |
| email: str |
| password: str |
| totp_code: str | None = None |
|
|
|
|
| class CreateAdminRequest(BaseModel): |
| email: str |
| password: str |
| role: str = "viewer" |
| ip_allowlist: list[str] = Field(default_factory=list) |
|
|
|
|
| class UpdateAdminRequest(BaseModel): |
| role: str | None = None |
| is_active: bool | None = None |
| ip_allowlist: list[str] | None = None |
| two_factor_enabled: bool | None = None |
|
|
|
|
| class IPBlockRequest(BaseModel): |
| ip: str |
| reason: str = "" |
| duration_hours: int = 24 |
|
|
|
|
| class ConfigUpdateRequest(BaseModel): |
| key: str |
| value: str |
| category: str = "general" |
|
|
|
|
| class AnnouncementRequest(BaseModel): |
| title: str |
| content: str |
| type: str = "info" |
| target_audience: str = "all" |
| expires_at: str | None = None |
|
|
|
|
| class APIKeyCreateRequest(BaseModel): |
| name: str |
| scopes: list[str] = Field(default_factory=list) |
| expires_days: int = 30 |
|
|
|
|
| class WebhookConfigRequest(BaseModel): |
| url: str |
| events: list[str] = Field(default_factory=list) |
| secret: str = "" |
| active: bool = True |
|
|
|
|
| |
|
|
|
|
| def _get_client_info(request: Request) -> tuple: |
| """Get client IP and user agent.""" |
| ip = request.client.host if request.client else "" |
| |
| forwarded = request.headers.get("X-Forwarded-For", "") |
| if forwarded: |
| ip = forwarded.split(",")[0].strip() |
| ua = request.headers.get("user-agent", "") |
| return ip, ua |
|
|
|
|
| |
| |
| |
|
|
|
|
| @router.post("/auth/login") |
| async def admin_login(request: Request, body: AdminLoginRequest): |
| """Admin login with email/password + optional 2FA.""" |
| ip, ua = _get_client_info(request) |
|
|
| |
| block_info = await SecurityManager.is_ip_blocked(ip) |
| if block_info: |
| raise HTTPException(status_code=403, detail="IP blocked") |
|
|
| |
| rate_check = await SecurityManager.check_rate_limit(f"login:{ip}", "login") |
| if not rate_check["allowed"]: |
| raise HTTPException(status_code=429, detail="Too many login attempts") |
|
|
| |
| admin = await AdminUserStore.verify_admin_login(body.email, body.password) |
| if not admin: |
| await SecurityManager.track_failed_login(f"login:{ip}") |
| raise HTTPException(status_code=401, detail="Invalid credentials") |
|
|
| |
| if admin.get("two_factor_enabled") and not body.totp_code: |
| raise HTTPException(status_code=401, detail="2FA code required") |
| |
| |
|
|
| |
| allowlist = admin.get("ip_allowlist", []) |
| if allowlist and ip not in allowlist: |
| await AuditLogger.log( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| action="admin.login_denied", |
| resource_type="ip", |
| resource_id=ip, |
| ip_address=ip, |
| user_agent=ua, |
| status="denied", |
| reason="IP not in allowlist", |
| ) |
| raise HTTPException(status_code=403, detail="IP not authorized") |
|
|
| |
| await SecurityManager.reset_failed_login(f"login:{ip}") |
|
|
| |
| session_id = await SessionManager.create_session( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| role=AdminRole(admin.get("role", "viewer")), |
| ip_address=ip, |
| user_agent=ua, |
| ) |
|
|
| |
| await AuditLogger.log( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| action="admin.login", |
| resource_type="session", |
| resource_id=session_id, |
| ip_address=ip, |
| user_agent=ua, |
| status="success", |
| ) |
|
|
| return { |
| "success": True, |
| "session_id": session_id, |
| "admin": {k: v for k, v in admin.items() if k not in ["password_hash", "two_factor_secret"]}, |
| "expires_in": SessionManager.SESSION_TIMEOUT_HOURS * 3600, |
| } |
|
|
|
|
| @router.post("/auth/logout") |
| async def admin_logout(request: Request): |
| """Logout current session.""" |
| session_id = request.headers.get("X-Admin-Session", "") |
| if session_id: |
| await SessionManager.destroy_session(session_id) |
| return {"success": True, "message": "Logged out"} |
|
|
|
|
| @router.post("/auth/logout-all") |
| async def admin_logout_all(request: Request): |
| """Logout all sessions (force logout everywhere).""" |
| auth = await require_admin(request, "system.write", AdminRole.ADMIN) |
| admin = auth["admin"] |
|
|
| count = await SessionManager.destroy_all_sessions(admin["id"]) |
|
|
| await AuditLogger.log( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| action="admin.logout_all", |
| resource_type="session", |
| resource_id=admin["id"], |
| ip_address=_get_client_info(request)[0], |
| status="success", |
| reason=f"Destroyed {count} sessions", |
| ) |
|
|
| return {"success": True, "sessions_destroyed": count} |
|
|
|
|
| @router.get("/auth/me") |
| async def admin_me(request: Request): |
| """Get current admin info.""" |
| auth = await require_admin(request) |
| admin = auth["admin"] |
|
|
| |
| sessions = await SessionManager.get_active_sessions(admin["id"]) |
|
|
| return { |
| "admin": {k: v for k, v in admin.items() if k not in ["password_hash", "two_factor_secret"]}, |
| "active_sessions": len(sessions), |
| "sessions": [ |
| { |
| "session_id": s["session_id"], |
| "ip_address": s["ip_address"], |
| "created_at": s["created_at"], |
| "last_active": s["last_active"], |
| } |
| for s in sessions |
| ], |
| "permissions": list(PERMISSIONS.get(AdminRole(admin.get("role", "viewer")), [])), |
| } |
|
|
|
|
| @router.get("/auth/sessions") |
| async def admin_sessions(request: Request): |
| """Get all active sessions for current admin.""" |
| auth = await require_admin(request) |
| sessions = await SessionManager.get_active_sessions(auth["admin"]["id"]) |
| return {"sessions": sessions, "total": len(sessions)} |
|
|
|
|
| |
| |
| |
|
|
|
|
| @router.get("/dashboard") |
| async def admin_dashboard(request: Request): |
| """Get admin dashboard metrics.""" |
| await require_admin(request, "dashboard.read") |
|
|
| |
| health = await SystemHealthMonitor.get_system_health() |
|
|
| |
| stats = {} |
| try: |
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
|
|
| stats = { |
| "total_users": await r.scard("rmi:users") or 0, |
| "total_admins": len(await r.hgetall("rmi:admins")), |
| "active_sessions": 0, |
| "blocked_ips": await r.scard("blocked_ips:all") or 0, |
| "total_deployments": await r.scard("token_deployments:all") or 0, |
| "total_scans_today": 0, |
| "x402_payments_today": 0, |
| } |
|
|
| |
| for key in await r.keys("admin_session:*"): |
| if not key.startswith("admin_sessions:"): |
| stats["active_sessions"] += 1 |
|
|
| except Exception as e: |
| logger.error(f"Dashboard stats error: {e}") |
|
|
| return { |
| "health": health, |
| "stats": stats, |
| "timestamp": datetime.utcnow().isoformat(), |
| } |
|
|
|
|
| @router.get("/dashboard/metrics") |
| async def dashboard_metrics(request: Request, hours: int = 24): |
| """Get time-series metrics for dashboard charts.""" |
| await require_admin(request, "analytics.read") |
|
|
| metrics = { |
| "api_requests": [], |
| "scans": [], |
| "payments": [], |
| "new_users": [], |
| "errors": [], |
| } |
|
|
| |
| |
|
|
| return { |
| "hours": hours, |
| "metrics": metrics, |
| "generated_at": datetime.utcnow().isoformat(), |
| } |
|
|
|
|
| |
| |
| |
|
|
|
|
| @router.get("/users") |
| async def list_users( |
| request: Request, |
| limit: int = 100, |
| offset: int = 0, |
| search: str = "", |
| tier: str = "", |
| banned: bool | None = None, |
| ): |
| """List all users with filtering.""" |
| await require_admin(request, "users.read") |
|
|
| try: |
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
|
|
| users = [] |
| all_users = await r.hgetall("rmi:users") |
|
|
| for _user_id, data in all_users.items(): |
| user = json.loads(data) |
|
|
| |
| if search and search.lower() not in user.get("email", "").lower(): |
| continue |
| if tier and user.get("tier", "") != tier: |
| continue |
| if banned is not None and user.get("banned", False) != banned: |
| continue |
|
|
| |
| safe_user = {k: v for k, v in user.items() if "password" not in k and "secret" not in k} |
| users.append(safe_user) |
|
|
| total = len(users) |
| users = users[offset : offset + limit] |
|
|
| return { |
| "users": users, |
| "total": total, |
| "limit": limit, |
| "offset": offset, |
| } |
|
|
| except Exception as e: |
| logger.error(f"List users error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.get("/users/{user_id}") |
| async def get_user(request: Request, user_id: str): |
| """Get user details.""" |
| await require_admin(request, "users.read") |
|
|
| try: |
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
|
|
| data = await r.hget("rmi:users", user_id) |
| if not data: |
| raise HTTPException(status_code=404, detail="User not found") |
|
|
| user = json.loads(data) |
| safe_user = {k: v for k, v in user.items() if "password" not in k and "secret" not in k} |
|
|
| return {"user": safe_user} |
|
|
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"Get user error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/users/{user_id}/ban") |
| async def ban_user(request: Request, user_id: str, body: dict = Body(...)): |
| """Ban or unban a user.""" |
| auth = await require_admin(request, "users.ban", AdminRole.MODERATOR) |
| admin = auth["admin"] |
| ip, ua = _get_client_info(request) |
|
|
| try: |
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
|
|
| data = await r.hget("rmi:users", user_id) |
| if not data: |
| raise HTTPException(status_code=404, detail="User not found") |
|
|
| user = json.loads(data) |
| before_state = {"banned": user.get("banned", False)} |
|
|
| user["banned"] = body.get("banned", True) |
| user["banned_at"] = datetime.utcnow().isoformat() if user["banned"] else None |
| user["banned_by"] = admin["id"] if user["banned"] else None |
| user["ban_reason"] = body.get("reason", "") |
|
|
| await r.hset("rmi:users", user_id, json.dumps(user)) |
|
|
| |
| await AuditLogger.log( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| action="user.ban" if user["banned"] else "user.unban", |
| resource_type="user", |
| resource_id=user_id, |
| ip_address=ip, |
| user_agent=ua, |
| before_state=before_state, |
| after_state={"banned": user["banned"]}, |
| ) |
|
|
| return {"success": True, "banned": user["banned"]} |
|
|
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"Ban user error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/users/{user_id}/tier") |
| async def update_user_tier(request: Request, user_id: str, body: dict = Body(...)): |
| """Update user tier/subscription.""" |
| auth = await require_admin(request, "users.write", AdminRole.ADMIN) |
| admin = auth["admin"] |
| ip, ua = _get_client_info(request) |
|
|
| try: |
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
|
|
| data = await r.hget("rmi:users", user_id) |
| if not data: |
| raise HTTPException(status_code=404, detail="User not found") |
|
|
| user = json.loads(data) |
| before_state = {"tier": user.get("tier", "FREE")} |
|
|
| user["tier"] = body.get("tier", "FREE") |
| user["tier_updated_at"] = datetime.utcnow().isoformat() |
| user["tier_updated_by"] = admin["id"] |
|
|
| await r.hset("rmi:users", user_id, json.dumps(user)) |
|
|
| await AuditLogger.log( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| action="user.tier_update", |
| resource_type="user", |
| resource_id=user_id, |
| ip_address=ip, |
| user_agent=ua, |
| before_state=before_state, |
| after_state={"tier": user["tier"]}, |
| ) |
|
|
| return {"success": True, "tier": user["tier"]} |
|
|
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"Update tier error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
| |
| |
|
|
|
|
| @router.get("/security/audit-logs") |
| async def get_audit_logs( |
| request: Request, |
| limit: int = 100, |
| offset: int = 0, |
| action: str = "", |
| admin_id: str = "", |
| start_date: str = "", |
| end_date: str = "", |
| ): |
| """Query audit logs.""" |
| await require_admin(request, "security.read", AdminRole.ADMIN) |
|
|
| entries = await AuditLogger.query( |
| admin_id=admin_id or None, |
| action=action or None, |
| limit=limit, |
| offset=offset, |
| ) |
|
|
| return { |
| "logs": [e.to_dict() for e in entries], |
| "total": len(entries), |
| "limit": limit, |
| "offset": offset, |
| } |
|
|
|
|
| @router.get("/security/blocked-ips") |
| async def get_blocked_ips(request: Request): |
| """List all blocked IPs.""" |
| await require_admin(request, "security.read", AdminRole.ADMIN) |
|
|
| try: |
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
|
|
| ips = await r.smembers("blocked_ips:all") |
| blocked = [] |
|
|
| for ip in ips: |
| data = await r.get(f"blocked_ip:{ip}") |
| if data: |
| blocked.append(json.loads(data)) |
|
|
| return {"blocked_ips": blocked, "total": len(blocked)} |
|
|
| except Exception as e: |
| logger.error(f"Get blocked IPs error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/security/block-ip") |
| async def block_ip(request: Request, body: IPBlockRequest): |
| """Block an IP address.""" |
| auth = await require_admin(request, "security.write", AdminRole.ADMIN) |
| admin = auth["admin"] |
| ip, ua = _get_client_info(request) |
|
|
| result = await SecurityManager.block_ip(body.ip, body.reason, body.duration_hours) |
|
|
| await AuditLogger.log( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| action="ip.block", |
| resource_type="ip", |
| resource_id=body.ip, |
| ip_address=ip, |
| user_agent=ua, |
| after_state={"reason": body.reason, "duration": body.duration_hours}, |
| ) |
|
|
| return {"success": result, "ip": body.ip, "duration_hours": body.duration_hours} |
|
|
|
|
| @router.post("/security/unblock-ip") |
| async def unblock_ip(request: Request, body: dict = Body(...)): |
| """Unblock an IP address.""" |
| auth = await require_admin(request, "security.write", AdminRole.ADMIN) |
| admin = auth["admin"] |
| ip, ua = _get_client_info(request) |
|
|
| target_ip = body.get("ip", "") |
| result = await SecurityManager.unblock_ip(target_ip) |
|
|
| await AuditLogger.log( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| action="ip.unblock", |
| resource_type="ip", |
| resource_id=target_ip, |
| ip_address=ip, |
| user_agent=ua, |
| ) |
|
|
| return {"success": result, "ip": target_ip} |
|
|
|
|
| @router.get("/security/threats") |
| async def get_threats(request: Request, hours: int = 24): |
| """Get security threats and alerts.""" |
| await require_admin(request, "security.read", AdminRole.ADMIN) |
|
|
| |
| threats = [] |
|
|
| return { |
| "threats": threats, |
| "total": len(threats), |
| "hours": hours, |
| } |
|
|
|
|
| |
| |
| |
|
|
|
|
| @router.get("/system/health") |
| async def system_health(request: Request): |
| """Get detailed system health.""" |
| await require_admin(request, "system.read") |
|
|
| health = await SystemHealthMonitor.get_system_health() |
| return health |
|
|
|
|
| @router.get("/system/config") |
| async def get_config(request: Request): |
| """Get system configuration (safe vars only).""" |
| await require_admin(request, "settings.read", AdminRole.ADMIN) |
|
|
| |
| safe_config = { |
| "environment": os.getenv("ENVIRONMENT", "production"), |
| "backend_port": os.getenv("BACKEND_PORT", "8000"), |
| "redis_host": os.getenv("REDIS_HOST", "localhost"), |
| "supabase_url": os.getenv("SUPABASE_URL", ""), |
| "x402_enabled": bool(os.getenv("X402_EVM_PAY_TO", "")), |
| "features": { |
| "rag": True, |
| "token_deployer": True, |
| "airdrop": True, |
| "x402": True, |
| "news": True, |
| }, |
| } |
|
|
| return {"config": safe_config} |
|
|
|
|
| @router.post("/system/config") |
| async def update_config(request: Request, body: ConfigUpdateRequest): |
| """Update system configuration.""" |
| auth = await require_admin(request, "settings.write", AdminRole.SUPERADMIN) |
| admin = auth["admin"] |
| ip, ua = _get_client_info(request) |
|
|
| |
| |
|
|
| await AuditLogger.log( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| action="config.update", |
| resource_type="config", |
| resource_id=body.key, |
| ip_address=ip, |
| user_agent=ua, |
| after_state={"key": body.key, "value": body.value, "category": body.category}, |
| ) |
|
|
| return {"success": True, "key": body.key, "updated": True} |
|
|
|
|
| @router.get("/system/services") |
| async def get_services(request: Request): |
| """Get status of all system services.""" |
| await require_admin(request, "system.read") |
|
|
| services = { |
| "backend": {"status": "running", "pid": os.getpid()}, |
| "redis": await SystemHealthMonitor._check_redis(), |
| "supabase": await SystemHealthMonitor._check_supabase(), |
| } |
|
|
| return {"services": services} |
|
|
|
|
| @router.post("/system/restart") |
| async def restart_service(request: Request, body: dict = Body(...)): |
| """Restart a service (simulated).""" |
| auth = await require_admin(request, "system.write", AdminRole.SUPERADMIN) |
| admin = auth["admin"] |
| ip, ua = _get_client_info(request) |
|
|
| service = body.get("service", "") |
|
|
| await AuditLogger.log( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| action="system.restart", |
| resource_type="service", |
| resource_id=service, |
| ip_address=ip, |
| user_agent=ua, |
| ) |
|
|
| return {"success": True, "service": service, "status": "restart_queued"} |
|
|
|
|
| |
| |
| |
|
|
|
|
| @router.get("/content/announcements") |
| async def get_announcements(request: Request, active_only: bool = True): |
| """Get announcements.""" |
| await require_admin(request, "content.read") |
|
|
| try: |
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
|
|
| announcements = [] |
| data = await r.get("rmi:announcements") |
| if data: |
| all_announcements = json.loads(data) |
| for a in all_announcements: |
| if not active_only or a.get("active", True): |
| announcements.append(a) |
|
|
| return {"announcements": announcements} |
|
|
| except Exception as e: |
| logger.error(f"Get announcements error: {e}") |
| return {"announcements": []} |
|
|
|
|
| @router.post("/content/announcements") |
| async def create_announcement(request: Request, body: AnnouncementRequest): |
| """Create an announcement.""" |
| auth = await require_admin(request, "content.write", AdminRole.MODERATOR) |
| admin = auth["admin"] |
| ip, ua = _get_client_info(request) |
|
|
| try: |
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
|
|
| announcement = { |
| "id": f"ann_{int(time.time())}", |
| "title": body.title, |
| "content": body.content, |
| "type": body.type, |
| "target_audience": body.target_audience, |
| "created_at": datetime.utcnow().isoformat(), |
| "created_by": admin["id"], |
| "active": True, |
| "expires_at": body.expires_at, |
| } |
|
|
| |
| data = await r.get("rmi:announcements") |
| announcements = json.loads(data) if data else [] |
| announcements.append(announcement) |
|
|
| await r.set("rmi:announcements", json.dumps(announcements)) |
|
|
| await AuditLogger.log( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| action="content.announcement.create", |
| resource_type="announcement", |
| resource_id=announcement["id"], |
| ip_address=ip, |
| user_agent=ua, |
| ) |
|
|
| return {"success": True, "announcement": announcement} |
|
|
| except Exception as e: |
| logger.error(f"Create announcement error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
| |
| |
|
|
|
|
| @router.get("/financial/x402") |
| async def x402_analytics(request: Request, days: int = 30): |
| """Get x402 payment analytics.""" |
| await require_admin(request, "financial.read", AdminRole.ADMIN) |
|
|
| |
| analytics = { |
| "total_payments": 0, |
| "total_revenue_usd": 0, |
| "payments_by_chain": {}, |
| "payments_by_tool": {}, |
| "daily_volume": [], |
| } |
|
|
| return {"analytics": analytics, "days": days} |
|
|
|
|
| @router.get("/financial/revenue") |
| async def revenue_report(request: Request, period: str = "month"): |
| """Get revenue report.""" |
| await require_admin(request, "financial.read", AdminRole.ADMIN) |
|
|
| return { |
| "period": period, |
| "revenue": { |
| "x402": 0, |
| "subscriptions": 0, |
| "api_usage": 0, |
| "total": 0, |
| }, |
| } |
|
|
|
|
| |
| |
| |
|
|
|
|
| @router.get("/api-keys") |
| async def list_api_keys(request: Request): |
| """List all API keys.""" |
| await require_admin(request, "api_keys.read", AdminRole.ADMIN) |
|
|
| try: |
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
|
|
| keys = [] |
| all_keys = await r.hgetall("rmi:api_keys") |
| for _key_id, data in all_keys.items(): |
| key_data = json.loads(data) |
| |
| key_data["key"] = key_data.get("key", "")[:8] + "..." + key_data.get("key", "")[-4:] |
| keys.append(key_data) |
|
|
| return {"api_keys": keys, "total": len(keys)} |
|
|
| except Exception as e: |
| logger.error(f"List API keys error: {e}") |
| return {"api_keys": [], "total": 0} |
|
|
|
|
| @router.post("/api-keys") |
| async def create_api_key(request: Request, body: APIKeyCreateRequest): |
| """Create a new API key.""" |
| auth = await require_admin(request, "api_keys.write", AdminRole.ADMIN) |
| admin = auth["admin"] |
| ip, ua = _get_client_info(request) |
|
|
| key_id = f"key_{secrets.token_hex(8)}" |
| api_key = f"rmi_{secrets.token_urlsafe(32)}" |
|
|
| key_data = { |
| "id": key_id, |
| "name": body.name, |
| "key": api_key, |
| "scopes": body.scopes, |
| "created_at": datetime.utcnow().isoformat(), |
| "created_by": admin["id"], |
| "expires_at": (datetime.utcnow() + timedelta(days=body.expires_days)).isoformat(), |
| "last_used": None, |
| "usage_count": 0, |
| "active": True, |
| } |
|
|
| try: |
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
|
|
| await r.hset("rmi:api_keys", key_id, json.dumps(key_data)) |
|
|
| await AuditLogger.log( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| action="api_key.create", |
| resource_type="api_key", |
| resource_id=key_id, |
| ip_address=ip, |
| user_agent=ua, |
| ) |
|
|
| |
| return { |
| "success": True, |
| "api_key": api_key, |
| "key_id": key_id, |
| "expires_at": key_data["expires_at"], |
| } |
|
|
| except Exception as e: |
| logger.error(f"Create API key error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/api-keys/{key_id}/revoke") |
| async def revoke_api_key(request: Request, key_id: str): |
| """Revoke an API key.""" |
| auth = await require_admin(request, "api_keys.write", AdminRole.ADMIN) |
| admin = auth["admin"] |
| ip, ua = _get_client_info(request) |
|
|
| try: |
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
|
|
| data = await r.hget("rmi:api_keys", key_id) |
| if not data: |
| raise HTTPException(status_code=404, detail="API key not found") |
|
|
| key_data = json.loads(data) |
| key_data["active"] = False |
| key_data["revoked_at"] = datetime.utcnow().isoformat() |
| key_data["revoked_by"] = admin["id"] |
|
|
| await r.hset("rmi:api_keys", key_id, json.dumps(key_data)) |
|
|
| await AuditLogger.log( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| action="api_key.revoke", |
| resource_type="api_key", |
| resource_id=key_id, |
| ip_address=ip, |
| user_agent=ua, |
| ) |
|
|
| return {"success": True, "revoked": True} |
|
|
| except HTTPException: |
| raise |
| except Exception as e: |
| logger.error(f"Revoke API key error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
| |
| |
|
|
|
|
| @router.get("/admins") |
| async def list_admins(request: Request): |
| """List all admin users.""" |
| await require_admin(request, "*", AdminRole.SUPERADMIN) |
|
|
| admins = await AdminUserStore.list_admins() |
|
|
| |
| safe_admins = [] |
| for admin in admins: |
| safe = {k: v for k, v in admin.items() if k not in ["password_hash", "two_factor_secret"]} |
| safe_admins.append(safe) |
|
|
| return {"admins": safe_admins, "total": len(safe_admins)} |
|
|
|
|
| @router.post("/admins") |
| async def create_admin(request: Request, body: CreateAdminRequest): |
| """Create a new admin user.""" |
| auth = await require_admin(request, "*", AdminRole.SUPERADMIN) |
| creator = auth["admin"] |
| ip, ua = _get_client_info(request) |
|
|
| role = AdminRole(body.role) if body.role in [r.value for r in AdminRole] else AdminRole.VIEWER |
|
|
| admin = await AdminUserStore.create_admin( |
| email=body.email, |
| password=body.password, |
| role=role, |
| created_by=creator["id"], |
| ) |
|
|
| if not admin: |
| raise HTTPException(status_code=400, detail="Email already exists") |
|
|
| await AuditLogger.log( |
| admin_id=creator["id"], |
| admin_email=creator["email"], |
| action="admin.create", |
| resource_type="admin", |
| resource_id=admin["id"], |
| ip_address=ip, |
| user_agent=ua, |
| after_state={"role": admin["role"], "email": admin["email"]}, |
| ) |
|
|
| return {"success": True, "admin": admin} |
|
|
|
|
| @router.post("/admins/{admin_id}/update") |
| async def update_admin(request: Request, admin_id: str, body: UpdateAdminRequest): |
| """Update an admin user.""" |
| auth = await require_admin(request, "*", AdminRole.SUPERADMIN) |
| updater = auth["admin"] |
| ip, ua = _get_client_info(request) |
|
|
| |
| if admin_id == updater["id"] and body.role and body.role != updater["role"]: |
| raise HTTPException(status_code=400, detail="Cannot change your own role") |
|
|
| admin = await AdminUserStore.get_admin(admin_id) |
| if not admin: |
| raise HTTPException(status_code=404, detail="Admin not found") |
|
|
| before_state = {} |
| after_state = {} |
|
|
| if body.role is not None: |
| before_state["role"] = admin["role"] |
| admin["role"] = body.role |
| after_state["role"] = body.role |
|
|
| if body.is_active is not None: |
| before_state["is_active"] = admin.get("is_active", True) |
| admin["is_active"] = body.is_active |
| after_state["is_active"] = body.is_active |
|
|
| if body.ip_allowlist is not None: |
| admin["ip_allowlist"] = body.ip_allowlist |
| after_state["ip_allowlist"] = body.ip_allowlist |
|
|
| if body.two_factor_enabled is not None: |
| admin["two_factor_enabled"] = body.two_factor_enabled |
| after_state["two_factor_enabled"] = body.two_factor_enabled |
|
|
| await AdminUserStore.save_admin(admin) |
|
|
| await AuditLogger.log( |
| admin_id=updater["id"], |
| admin_email=updater["email"], |
| action="admin.update", |
| resource_type="admin", |
| resource_id=admin_id, |
| ip_address=ip, |
| user_agent=ua, |
| before_state=before_state, |
| after_state=after_state, |
| ) |
|
|
| return {"success": True, "admin_id": admin_id} |
|
|
|
|
| @router.post("/admins/{admin_id}/reset-password") |
| async def reset_admin_password(request: Request, admin_id: str, body: dict = Body(...)): |
| """Reset an admin's password.""" |
| auth = await require_admin(request, "*", AdminRole.SUPERADMIN) |
| updater = auth["admin"] |
| ip, ua = _get_client_info(request) |
|
|
| from app.auth import hash_password |
|
|
| admin = await AdminUserStore.get_admin(admin_id) |
| if not admin: |
| raise HTTPException(status_code=404, detail="Admin not found") |
|
|
| new_password = body.get("password", "") |
| if len(new_password) < 8: |
| raise HTTPException(status_code=400, detail="Password must be at least 8 characters") |
|
|
| admin["password_hash"] = hash_password(new_password) |
| admin["password_changed_at"] = datetime.utcnow().isoformat() |
|
|
| await AdminUserStore.save_admin(admin) |
|
|
| |
| await SessionManager.destroy_all_sessions(admin_id) |
|
|
| await AuditLogger.log( |
| admin_id=updater["id"], |
| admin_email=updater["email"], |
| action="admin.password_reset", |
| resource_type="admin", |
| resource_id=admin_id, |
| ip_address=ip, |
| user_agent=ua, |
| ) |
|
|
| return {"success": True, "sessions_destroyed": True} |
|
|
|
|
| |
| |
| |
|
|
|
|
| @router.get("/backups") |
| async def list_backups(request: Request): |
| """List available backups.""" |
| await require_admin(request, "backups.read", AdminRole.SUPERADMIN) |
|
|
| return {"backups": [], "total": 0} |
|
|
|
|
| @router.post("/backups/create") |
| async def create_backup(request: Request, body: dict = Body(...)): |
| """Create a new backup.""" |
| auth = await require_admin(request, "backups.write", AdminRole.SUPERADMIN) |
| admin = auth["admin"] |
| ip, ua = _get_client_info(request) |
|
|
| backup_type = body.get("type", "full") |
|
|
| await AuditLogger.log( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| action="backup.create", |
| resource_type="backup", |
| resource_id=backup_type, |
| ip_address=ip, |
| user_agent=ua, |
| ) |
|
|
| return {"success": True, "backup_type": backup_type, "status": "queued"} |
|
|
|
|
| |
| |
| |
|
|
|
|
| @router.get("/webhooks") |
| async def list_webhooks(request: Request): |
| """List configured webhooks.""" |
| await require_admin(request, "webhooks.read", AdminRole.ADMIN) |
|
|
| try: |
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
|
|
| webhooks = [] |
| data = await r.get("rmi:webhooks") |
| if data: |
| webhooks = json.loads(data) |
|
|
| return {"webhooks": webhooks} |
|
|
| except Exception as e: |
| logger.error(f"List webhooks error: {e}") |
| return {"webhooks": []} |
|
|
|
|
| @router.post("/webhooks") |
| async def create_webhook(request: Request, body: WebhookConfigRequest): |
| """Create a webhook.""" |
| auth = await require_admin(request, "webhooks.write", AdminRole.ADMIN) |
| admin = auth["admin"] |
| ip, ua = _get_client_info(request) |
|
|
| webhook = { |
| "id": f"wh_{int(time.time())}", |
| "url": body.url, |
| "events": body.events, |
| "secret": body.secret, |
| "active": body.active, |
| "created_at": datetime.utcnow().isoformat(), |
| "created_by": admin["id"], |
| } |
|
|
| try: |
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
|
|
| data = await r.get("rmi:webhooks") |
| webhooks = json.loads(data) if data else [] |
| webhooks.append(webhook) |
|
|
| await r.set("rmi:webhooks", json.dumps(webhooks)) |
|
|
| await AuditLogger.log( |
| admin_id=admin["id"], |
| admin_email=admin["email"], |
| action="webhook.create", |
| resource_type="webhook", |
| resource_id=webhook["id"], |
| ip_address=ip, |
| user_agent=ua, |
| ) |
|
|
| return {"success": True, "webhook": webhook} |
|
|
| except Exception as e: |
| logger.error(f"Create webhook error: {e}") |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| |
| |
| |
|
|
|
|
| @router.get("/daily-briefing") |
| async def get_daily_briefing(request: Request): |
| """Get daily intel briefing for the dev with real system metrics.""" |
| await require_admin(request, "intel.read", AdminRole.ADMIN) |
|
|
| from datetime import datetime |
|
|
| import psutil |
|
|
| cpu_percent = psutil.cpu_percent(interval=0.1) |
| memory = psutil.virtual_memory() |
| disk = psutil.disk_usage("/") |
|
|
| boot_time = datetime.fromtimestamp(psutil.boot_time()) |
| uptime_seconds = (datetime.now() - boot_time).total_seconds() |
| days = int(uptime_seconds // 86400) |
| hours = int((uptime_seconds % 86400) // 3600) |
| mins = int((uptime_seconds % 3600) // 60) |
| uptime_str = f"{days}d {hours}h {mins}m" |
|
|
| return { |
| "date": datetime.utcnow().strftime("%Y-%m-%d"), |
| "summary": "System operating within normal parameters. Real-time metrics integrated.", |
| "items": [ |
| { |
| "id": "1", |
| "type": "info", |
| "category": "dev", |
| "title": "Real-time Metrics Active", |
| "description": "Daily briefing now pulls live CPU, memory, and disk usage.", |
| "action": "Monitor metrics", |
| }, |
| ], |
| "focus_areas": [ |
| "Complete Wyoming DAO LLC transition documentation", |
| "Review and publish pending Ghost CMS longform content", |
| "Monitor new token scanner performance on Base chain", |
| ], |
| "server_status": { |
| "cpu": round(cpu_percent, 1), |
| "memory": round(memory.percent, 1), |
| "disk": round(disk.percent, 1), |
| "uptime": uptime_str, |
| }, |
| } |
|
|
|
|
| @router.post("/daily-briefing/generate") |
| async def generate_daily_briefing(request: Request): |
| """Trigger regeneration of daily briefing.""" |
| await require_admin(request, "intel.write", AdminRole.ADMIN) |
| return {"success": True, "message": "Briefing regenerated"} |
|
|
|
|
| @router.get("/dao-transition") |
| async def get_dao_transition(request: Request): |
| """Get DAO transition status and steps.""" |
| await require_admin(request, "config.read", AdminRole.ADMIN) |
| return { |
| "current_entity": "Rug Munch Intelligence LLC (Wyoming)", |
| "target_entity": "Rug Munch Intelligence DAO LLC (Wyoming)", |
| "status": "in_progress", |
| "progress": 35, |
| "steps": [ |
| { |
| "id": "1", |
| "title": "Legal Consultation & Feasibility", |
| "description": "Engage Wyoming DAO LLC specialist counsel.", |
| "status": "completed", |
| "details": [ |
| "Reviewed Wyoming DAO LLC Act", |
| "Identified required operating agreement amendments", |
| ], |
| "documents": ["wyoming_dao_llc_requirements.pdf"], |
| }, |
| { |
| "id": "2", |
| "title": "Operating Agreement Drafting", |
| "description": "Draft new DAO-compliant Operating Agreement.", |
| "status": "in_progress", |
| "details": ["Template acquired", "Pending customization for RMI tokenomics"], |
| "documents": [], |
| }, |
| { |
| "id": "3", |
| "title": "Articles of Organization Amendment", |
| "description": "File amendment with Wyoming Secretary of State.", |
| "status": "pending", |
| "details": ["Requires signed operating agreement first", "Filing fee: $100"], |
| "documents": [], |
| }, |
| { |
| "id": "4", |
| "title": "EIN Update & IRS Notification", |
| "description": "Update Employer Identification Number records.", |
| "status": "pending", |
| "details": ["Form 8822-B may be required"], |
| "documents": [], |
| }, |
| { |
| "id": "5", |
| "title": "Banking & Financial Institution Update", |
| "description": "Notify existing banking partners.", |
| "status": "pending", |
| "details": ["Prepare corporate resolution"], |
| "documents": [], |
| }, |
| ], |
| } |
|
|
|
|
| @router.patch("/dao-transition/steps/{step_id}") |
| async def update_dao_step(request: Request, step_id: str, body: dict = Body(...)): |
| """Update a DAO transition step status.""" |
| await require_admin(request, "config.write", AdminRole.ADMIN) |
| return {"success": True, "step_id": step_id, "status": body.get("status")} |
|
|
|
|
| @router.post("/content/generate") |
| async def generate_content(request: Request, body: dict = Body(...)): |
| """Generate content for Ghost CMS.""" |
| await require_admin(request, "content.write", AdminRole.ADMIN) |
| content_type = body.get("type", "twitter") |
| topic = body.get("topic", "") |
| body.get("tone", "professional") |
|
|
| mock_content = { |
| "twitter": f"π¨ {topic} is a critical topic for crypto security. Always verify contracts before interacting. #RMI #CryptoSecurity", |
| "thread": f"π§΅ Thread: {topic}\n\n1/ The crypto space is evolving rapidly, and {topic} represents a major shift.\n\n2/ Here's what you need to know to stay safe and informed.\n\n3/ Always DYOR and use tools like RMI to verify before you trust.", |
| "longform": f"# {topic}: A Comprehensive Guide\n\n## Introduction\n{topic} has become a focal point in the Web3 ecosystem. This guide breaks down the essentials.\n\n## Key Takeaways\n- Always verify smart contracts\n- Use multi-sig wallets for treasury management\n- Stay updated with RMI intelligence feeds\n\n## Conclusion\nSecurity is not an afterthought; it's the foundation.", |
| "newsletter": f"Subject: Weekly Intel: {topic}\n\nHey team,\n\nThis week's focus is on {topic}. We've seen a 40% increase in related activity. Here's what you need to know...", |
| } |
|
|
| return { |
| "content": mock_content.get(content_type, mock_content["twitter"]), |
| "metadata": { |
| "word_count": len(mock_content.get(content_type, "").split()), |
| "estimated_read_time": "1m" if content_type == "twitter" else "3m", |
| "hashtags": ["#RMI", "#CryptoSecurity", "#Web3"], |
| }, |
| } |
|
|
|
|
| @router.post("/wallets/generate") |
| async def generate_wallet(request: Request, body: dict = Body(...)): |
| """Generate a new wallet for a specific chain.""" |
| await require_admin(request, "wallets.write", AdminRole.ADMIN) |
| chain = body.get("chain", "ethereum") |
|
|
| import secrets |
|
|
| if chain == "solana": |
| address = "NewSol" + secrets.token_hex(20) |
| private_key = secrets.token_hex(32) + "solana_mock_pk" |
| else: |
| address = "0x" + secrets.token_hex(20) |
| private_key = "0x" + secrets.token_hex(32) |
|
|
| return { |
| "id": f"wallet_{secrets.token_hex(8)}", |
| "chain": chain, |
| "address": address, |
| "private_key": private_key, |
| "created_at": datetime.utcnow().strftime("%Y-%m-%d"), |
| "label": f"New {chain.capitalize()} Wallet", |
| } |
|
|
|
|
| @router.post("/contracts/generate") |
| async def generate_contract(request: Request, body: dict = Body(...)): |
| """Generate a smart contract template.""" |
| await require_admin(request, "contracts.write", AdminRole.ADMIN) |
| contract_type = body.get("type", "erc20") |
| chain = body.get("chain", "ethereum") |
| name = body.get("name", "MyToken") |
| symbol = body.get("symbol", "MTK") |
|
|
| templates = { |
| "erc20": f'// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport "@openzeppelin/contracts/token/ERC20/ERC20.sol";\nimport "@openzeppelin/contracts/access/Ownable.sol";\n\ncontract {name} is ERC20, Ownable {{\n constructor() ERC20("{name}", "{symbol}") Ownable(msg.sender) {{\n _mint(msg.sender, 1000000 * 10 ** decimals());\n }}\n\n function mint(address to, uint256 amount) public onlyOwner {{\n _mint(to, amount);\n }}\n}}', |
| "erc721": f'// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport "@openzeppelin/contracts/token/ERC721/ERC721.sol";\nimport "@openzeppelin/contracts/access/Ownable.sol";\n\ncontract {name}NFT is ERC721, Ownable {{\n uint256 private _nextTokenId;\n\n constructor() ERC721("{name}", "{symbol}") Ownable(msg.sender) {{}}\n\n function safeMint(address to) public onlyOwner {{\n uint256 tokenId = _nextTokenId++;\n _safeMint(to, tokenId);\n }}\n}}', |
| "multisig": '// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\ncontract MultiSigWallet {\n event Deposit(address indexed sender, uint amount);\n event SubmitTransaction(address indexed owner, uint indexed txIndex);\n event ConfirmTransaction(address indexed owner, uint indexed txIndex);\n\n address[] public owners;\n mapping(address => bool) public isOwner;\n uint public numConfirmationsRequired;\n\n struct Transaction {\n address to;\n uint value;\n bytes data;\n bool executed;\n uint numConfirmations;\n }\n\n mapping(uint => Transaction) public transactions;\n mapping(uint => mapping(address => bool)) public isConfirmed;\n\n constructor(address[] memory _owners, uint _numConfirmationsRequired) {\n require(_owners.length > 0, "Owners required");\n require(_numConfirmationsRequired > 0 && _numConfirmationsRequired <= _owners.length, "Invalid number of required confirmations");\n\n for (uint i = 0; i < _owners.length; i++) {\n address owner = _owners[i];\n require(owner != address(0), "Invalid owner");\n require(!isOwner[owner], "Owner not unique");\n isOwner[owner] = true;\n owners.push(owner);\n }\n numConfirmationsRequired = _numConfirmationsRequired;\n }\n}', |
| "timelock": '// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\ncontract TimelockController {\n uint256 public constant MIN_DELAY = 1 days;\n uint256 public constant MAX_DELAY = 30 days;\n uint256 public constant GRACE_PERIOD = 14 days;\n\n uint256 public delay;\n mapping(bytes32 => bool) public queuedTransactions;\n\n event QueueTransaction(bytes32 indexed txHash, address target, uint value, string signature, bytes data, uint eta);\n event ExecuteTransaction(bytes32 indexed txHash, address target, uint value, string signature, bytes data, uint eta);\n\n constructor(uint256 delay_) {\n require(delay_ >= MIN_DELAY, "Timelock::constructor: Delay must exceed minimum delay");\n require(delay_ <= MAX_DELAY, "Timelock::constructor: Delay must not exceed maximum delay");\n delay = delay_;\n }\n}', |
| "dao_governor": f'// SPDX-License-Identifier: MIT\npragma solidity ^0.8.20;\n\nimport "@openzeppelin/contracts/governance/Governor.sol";\nimport "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";\nimport "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";\nimport "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";\nimport "@openzeppelin/contracts/governance/extensions/GovernorVotesQuorumFraction.sol";\nimport "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol";\n\ncontract {name}Governor is Governor, GovernorSettings, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl {{\n constructor(IVotes _token, TimelockController _timelock)\n Governor("{name} Governor")\n GovernorSettings(1 /* 1 block */, 50400 /* 1 week */, 0)\n GovernorVotes(_token)\n GovernorVotesQuorumFraction(4)\n GovernorTimelockControl(_timelock)\n {{}}\n\n function votingDelay() public view override(Governor, GovernorSettings) returns (uint256) {{ return super.votingDelay(); }}\n function votingPeriod() public view override(Governor, GovernorSettings) returns (uint256) {{ return super.votingPeriod(); }}\n function quorum(uint256 blockNumber) public view override(Governor, GovernorVotesQuorumFraction) returns (uint256) {{ return super.quorum(blockNumber); }}\n function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {{ return super.proposalThreshold(); }}\n}}', |
| } |
|
|
| return { |
| "code": templates.get(contract_type, templates["erc20"]), |
| "metadata": { |
| "compiler": "solc 0.8.20", |
| "optimization": True, |
| "runs": 200, |
| "license": "MIT", |
| "chain": chain, |
| }, |
| } |
|
|
|
|
| |
| |
| |
|
|
| import uuid |
|
|
| from fastapi import File, UploadFile |
|
|
|
|
| @router.post("/upload/document") |
| async def upload_document(request: Request, file: UploadFile = File(...), step_id: str = ""): |
| """Upload a document (e.g., DAO transition PDF) with strict MIME-type validation.""" |
| await require_admin(request, "config.write", AdminRole.ADMIN) |
|
|
| upload_dir = "/root/backend/uploads/dao_documents" |
| os.makedirs(upload_dir, exist_ok=True) |
|
|
| |
| file_content = await file.read() |
| if len(file_content) < 4: |
| raise HTTPException(status_code=400, detail="File is too small or empty") |
|
|
| magic_bytes = file_content[:4] |
|
|
| |
| is_pdf = magic_bytes == b"%PDF" |
| is_docx = magic_bytes == b"PK\x03\x04" |
|
|
| if not (is_pdf or is_docx): |
| raise HTTPException(status_code=400, detail="Invalid file type. Only PDF and DOCX are allowed.") |
|
|
| ext = ".pdf" if is_pdf else ".docx" |
| unique_filename = f"{uuid.uuid4().hex}{ext}" |
| file_path = os.path.join(upload_dir, unique_filename) |
|
|
| with open(file_path, "wb") as f: |
| f.write(file_content) |
|
|
| return { |
| "success": True, |
| "filename": file.filename, |
| "url": f"/api/v1/admin/backend/uploads/dao_documents/{unique_filename}", |
| "step_id": step_id, |
| } |
|
|
|
|
| |
| |
| |
|
|
| import httpx |
|
|
|
|
| @router.post("/wallets/fetch-balances") |
| async def fetch_wallet_balances(request: Request, body: dict = Body(...)): |
| """Fetch real-time balances for a list of wallet addresses with RPC fallback.""" |
| await require_admin(request, "wallets.read", AdminRole.ADMIN) |
| wallets = body.get("wallets", []) |
|
|
| |
| rpc_fallbacks = { |
| "ethereum": [ |
| "https://eth.llamarpc.com", |
| "https://rpc.ankr.com/eth", |
| "https://cloudflare-eth.com", |
| ], |
| "base": [ |
| "https://mainnet.base.org", |
| "https://base.llamarpc.com", |
| "https://base.publicnode.com", |
| ], |
| "arbitrum": [ |
| "https://arb1.arbitrum.io/rpc", |
| "https://arbitrum.llamarpc.com", |
| "https://arbitrum.publicnode.com", |
| ], |
| "polygon": [ |
| "https://polygon-rpc.com", |
| "https://polygon.llamarpc.com", |
| "https://polygon.publicnode.com", |
| ], |
| } |
|
|
| results = [] |
| for wallet in wallets: |
| address = wallet.get("address", "") |
| chain = wallet.get("chain", "ethereum") |
| balance = "0.00" |
|
|
| try: |
| if chain in rpc_fallbacks: |
| payload = { |
| "jsonrpc": "2.0", |
| "method": "eth_getBalance", |
| "params": [address, "latest"], |
| "id": 1, |
| } |
|
|
| |
| success = False |
| for rpc_url in rpc_fallbacks[chain]: |
| try: |
| async with httpx.AsyncClient() as client: |
| res = await client.post(rpc_url, json=payload, timeout=5.0) |
| if res.status_code == 200: |
| res_data = res.json() |
| if "result" in res_data and res_data["result"] != "0x": |
| wei = int(res_data["result"], 16) |
| balance = f"{wei / 1e18:.4f}" |
| success = True |
| break |
| except Exception: |
| continue |
|
|
| if not success: |
| balance = "Error" |
| elif chain == "solana": |
| balance = "N/A" |
| except Exception: |
| balance = "Error" |
|
|
| results.append( |
| { |
| "id": wallet.get("id"), |
| "address": address, |
| "chain": chain, |
| "balance": f"{balance} {wallet.get('symbol', 'ETH').upper()}", |
| } |
| ) |
|
|
| return {"balances": results} |
|
|
|
|
| |
| |
| |
|
|
|
|
| @router.post("/contracts/deploy-testnet") |
| async def deploy_contract_testnet(request: Request, body: dict = Body(...)): |
| """Deploy a contract to testnet (mocked for safety).""" |
| await require_admin(request, "contracts.write", AdminRole.ADMIN) |
|
|
| chain = body.get("chain", "base") |
| name = body.get("name", "TestToken") |
|
|
| import secrets |
|
|
| mock_tx_hash = "0x" + secrets.token_hex(32) |
| mock_address = "0x" + secrets.token_hex(20) |
|
|
| explorer_url = "https://sepolia.basescan.org" |
| if chain == "ethereum": |
| explorer_url = "https://sepolia.etherscan.io" |
| elif chain == "arbitrum": |
| explorer_url = "https://sepolia.arbiscan.io" |
|
|
| return { |
| "success": True, |
| "tx_hash": mock_tx_hash, |
| "contract_address": mock_address, |
| "explorer_url": f"{explorer_url}/address/{mock_address}", |
| "message": f"Contract '{name}' deployed to {chain} testnet successfully!", |
| } |
|
|
|
|
| |
| |
| |
|
|
|
|
| @router.post("/emergency-lockdown") |
| async def emergency_lockdown(request: Request, body: dict = Body(...)): |
| """Toggle emergency lockdown mode. Blocks all non-admin traffic.""" |
| auth = await require_admin(request, "system.write", AdminRole.SUPERADMIN) |
|
|
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
|
|
| is_locked = body.get("locked", True) |
| reason = body.get("reason", "Manual lockdown triggered") |
|
|
| if is_locked: |
| await r.setex("rmi:emergency_lockdown", 86400, reason) |
| logger.warning(f"EMERGENCY LOCKDOWN ACTIVATED by {auth['admin']['email']}: {reason}") |
| else: |
| await r.delete("rmi:emergency_lockdown") |
| logger.info(f"EMERGENCY LOCKDOWN DEACTIVATED by {auth['admin']['email']}") |
|
|
| return {"success": True, "locked": is_locked, "reason": reason} |
|
|
|
|
| @router.get("/emergency-status") |
| async def emergency_status(request: Request): |
| """Check current emergency lockdown status.""" |
| import redis.asyncio as redis_lib |
|
|
| r = redis_lib.Redis( |
| host=os.getenv("REDIS_HOST", "localhost"), |
| port=int(os.getenv("REDIS_PORT", "6379")), |
| password=os.getenv("REDIS_PASSWORD", ""), |
| decode_responses=True, |
| ) |
| reason = await r.get("rmi:emergency_lockdown") |
| return {"locked": reason is not None, "reason": reason or ""} |
|
|