"""B01_Dashboard aiomysql Raw SQL 저장소.""" from __future__ import annotations import shutil from datetime import datetime, timedelta from typing import Any import aiomysql from config.config_db import get_db_pool def _role(value: str | None) -> str: return {"MASTER": "ADMIN", "MEMBER": "USER"}.get(value or "USER", value or "USER") def _stage_from_status(status: str | None) -> tuple[int, int]: order = [ ("FILE_UPLOADED", 1), ("WF1", 2), ("WF2", 3), ("WF3", 4), ("WF4", 5), ("WF5", 6), ("WF6", 7), ("DONE", 7), ("CONFIRMED", 7), ] value = status or "NEW" stage = 0 for token, idx in order: if token in value: stage = max(stage, idx) return stage, round(stage / 7 * 100) def _project_row(row: dict[str, Any]) -> dict[str, Any]: stage, progress = _stage_from_status(row.get("status")) return {**row, "workflow_stage": stage, "progress_percent": progress} async def get_dashboard_me(user_id: int) -> dict[str, Any] | None: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( """SELECT u.id, u.email, u.name, u.position, u.department, u.phone, u.company_id, u.role, u.is_master, u.status, u.last_login, u.auth_expires_at, c.name AS company_name FROM users u LEFT JOIN companies c ON c.id = u.company_id WHERE u.id = %s AND u.deleted_at IS NULL""", (user_id,), ) row = await cursor.fetchone() if row: row["role"] = _role(row.get("role")) row["is_master"] = bool(row.get("is_master")) return row async def update_user_profile(user_id: int, data: dict[str, Any]) -> dict[str, Any] | None: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor() as cursor: await cursor.execute( """UPDATE users SET name = %s, position = %s, department = %s, phone = %s WHERE id = %s AND deleted_at IS NULL""", ( data["name"], data.get("position"), data.get("department"), data.get("phone"), user_id, ), ) await connection.commit() return await get_dashboard_me(user_id) async def list_user_projects(user_id: int) -> list[dict[str, Any]]: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( """SELECT id, name, region, status, updated_at, created_at FROM projects WHERE user_id = %s AND deleted_at IS NULL ORDER BY updated_at DESC, created_at DESC""", (user_id,), ) return [_project_row(row) for row in await cursor.fetchall()] async def list_company_projects(company_id: int) -> list[dict[str, Any]]: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( """SELECT p.id, p.name, p.region, p.status, p.updated_at, p.created_at, u.name AS owner_name, u.email AS owner_email FROM projects p LEFT JOIN users u ON u.id = p.user_id WHERE p.company_id = %s AND p.deleted_at IS NULL ORDER BY p.updated_at DESC, p.created_at DESC""", (company_id,), ) return [_project_row(row) for row in await cursor.fetchall()] async def get_user_company(company_id: int | None) -> dict[str, Any] | None: if company_id is None: return None pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( """SELECT c.id, c.name, c.business_registration_number, c.business_address, c.business_owner, c.business_status, COUNT(DISTINCT u.id) AS user_count, COUNT(DISTINCT p.id) AS project_count FROM companies c LEFT JOIN users u ON u.company_id = c.id AND u.deleted_at IS NULL LEFT JOIN projects p ON p.company_id = c.id AND p.deleted_at IS NULL WHERE c.id = %s AND c.deleted_at IS NULL GROUP BY c.id""", (company_id,), ) return await cursor.fetchone() async def search_companies(query: str) -> list[dict[str, Any]]: pool = get_db_pool() pattern = f"%{query}%" async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( """SELECT id, name, business_registration_number, business_status FROM companies WHERE deleted_at IS NULL AND (name LIKE %s OR business_registration_number LIKE %s) ORDER BY name LIMIT 20""", (pattern, pattern), ) return list(await cursor.fetchall()) async def create_company(user_id: int, data: dict[str, Any]) -> dict[str, Any]: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor() as cursor: try: await connection.begin() await cursor.execute( """INSERT INTO companies (name, business_registration_number, business_address, business_owner, business_status, master_user_id, created_by, status) VALUES (%s, %s, %s, %s, '활동중', %s, %s, 'ACTIVE')""", ( data["name"], data["business_registration_number"], data.get("business_address"), data.get("business_owner"), user_id, user_id, ), ) company_id = cursor.lastrowid await cursor.execute( """UPDATE users SET company_id = %s, role = 'ADMIN', is_master = TRUE, status = 'ACTIVE', auth_expires_at = DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 3 MONTH) WHERE id = %s""", (company_id, user_id), ) await cursor.execute( """INSERT INTO system_audit_logs (user_id, action, resource_type, resource_id) VALUES (%s, 'COMPANY_CREATE', 'company', %s)""", (user_id, company_id), ) await connection.commit() return {"company_id": company_id, "status": "ACTIVE"} except Exception: await connection.rollback() raise async def join_company(user_id: int, company_id: int) -> dict[str, Any]: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor() as cursor: try: await connection.begin() await cursor.execute( """INSERT INTO join_requests (user_id, company_id, status) VALUES (%s, %s, 'PENDING') ON DUPLICATE KEY UPDATE status = 'PENDING', requested_at = CURRENT_TIMESTAMP, reviewed_by = NULL, reviewed_at = NULL""", (user_id, company_id), ) request_id = cursor.lastrowid await cursor.execute( "UPDATE users SET status = 'PENDING', company_id = NULL WHERE id = %s", (user_id,) ) await connection.commit() return {"join_request_id": request_id, "status": "PENDING"} except Exception: await connection.rollback() raise async def list_company_members(company_id: int) -> list[dict[str, Any]]: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( """SELECT id, email, name, position, department, role, is_master, status FROM users WHERE company_id = %s AND deleted_at IS NULL ORDER BY name, email""", (company_id,), ) rows = list(await cursor.fetchall()) for row in rows: row["role"] = _role(row.get("role")) row["is_master"] = bool(row.get("is_master")) return rows async def add_company_member(company_id: int, email: str) -> dict[str, Any] | None: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await connection.begin() await cursor.execute( """SELECT id, company_id FROM users WHERE email = %s AND deleted_at IS NULL FOR UPDATE""", (email.lower(),), ) user = await cursor.fetchone() if not user or user["company_id"] == company_id: await connection.rollback() return None await cursor.execute( """UPDATE users SET company_id = %s, status = 'ACTIVE', role = 'USER', is_master = FALSE WHERE id = %s""", (company_id, user["id"]), ) await connection.commit() return await get_dashboard_me(user["id"]) async def remove_company_member(company_id: int, user_id: int) -> bool: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor() as cursor: await cursor.execute( """UPDATE users SET company_id = NULL, status = 'NO_COMPANY', role = 'USER', is_master = FALSE WHERE id = %s AND company_id = %s AND is_master = FALSE""", (user_id, company_id), ) changed = cursor.rowcount > 0 await connection.commit() return changed async def list_join_requests(company_id: int | None = None) -> list[dict[str, Any]]: where = "WHERE jr.company_id = %s" if company_id else "" params = (company_id,) if company_id else () pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( f"""SELECT jr.id, jr.user_id, jr.company_id, jr.requested_at, jr.status, u.email AS user_email, u.name AS user_name, c.name AS company_name FROM join_requests jr JOIN users u ON u.id = jr.user_id JOIN companies c ON c.id = jr.company_id {where} ORDER BY jr.requested_at DESC LIMIT 100""", params, ) return list(await cursor.fetchall()) async def process_join_request( request_id: int, reviewer_id: int, approved: bool, company_id: int | None = None ) -> bool: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await connection.begin() where = "id = %s AND status = 'PENDING'" params: tuple[Any, ...] = (request_id,) if company_id is not None: where += " AND company_id = %s" params = (request_id, company_id) await cursor.execute(f"SELECT * FROM join_requests WHERE {where} FOR UPDATE", params) row = await cursor.fetchone() if not row: await connection.rollback() return False status = "APPROVED" if approved else "REJECTED" await cursor.execute( """UPDATE join_requests SET status = %s, reviewed_by = %s, reviewed_at = CURRENT_TIMESTAMP WHERE id = %s""", (status, reviewer_id, request_id), ) if approved: await cursor.execute( """UPDATE users SET company_id = %s, status = 'ACTIVE' WHERE id = %s""", (row["company_id"], row["user_id"]), ) else: await cursor.execute( "UPDATE users SET status = 'REJECTED' WHERE id = %s", (row["user_id"],) ) await connection.commit() return True async def list_all_companies() -> list[dict[str, Any]]: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( """SELECT c.id, c.name, c.business_registration_number, c.business_status, c.created_at, COUNT(DISTINCT u.id) AS user_count, COUNT(DISTINCT p.id) AS project_count FROM companies c LEFT JOIN users u ON u.company_id = c.id AND u.deleted_at IS NULL LEFT JOIN projects p ON p.company_id = c.id AND p.deleted_at IS NULL WHERE c.deleted_at IS NULL GROUP BY c.id ORDER BY c.created_at DESC""" ) return list(await cursor.fetchall()) async def list_all_users() -> list[dict[str, Any]]: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( """SELECT u.id, u.email, u.name, u.role, u.status, u.company_id, c.name AS company_name, u.last_login FROM users u LEFT JOIN companies c ON c.id = u.company_id WHERE u.deleted_at IS NULL ORDER BY u.created_at DESC LIMIT 200""" ) rows = list(await cursor.fetchall()) for row in rows: row["role"] = _role(row.get("role")) return rows async def change_user_role(user_id: int, role: str) -> bool: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor() as cursor: await cursor.execute("UPDATE users SET role = %s WHERE id = %s", (role, user_id)) changed = cursor.rowcount > 0 await connection.commit() return changed async def assign_user_company(user_id: int, company_id: int | None) -> bool: status = "ACTIVE" if company_id else "NO_COMPANY" pool = get_db_pool() async with pool.acquire() as connection, connection.cursor() as cursor: await cursor.execute( "UPDATE users SET company_id = %s, status = %s WHERE id = %s", (company_id, status, user_id), ) changed = cursor.rowcount > 0 await connection.commit() return changed async def list_audit_logs(limit: int, offset: int) -> dict[str, Any]: pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute("SELECT COUNT(*) AS total FROM system_audit_logs") total = (await cursor.fetchone())["total"] await cursor.execute( """SELECT l.id, l.user_id, u.email, l.action, l.resource_type, l.resource_id, l.timestamp FROM system_audit_logs l LEFT JOIN users u ON u.id = l.user_id ORDER BY l.timestamp DESC LIMIT %s OFFSET %s""", (limit, offset), ) return {"total": total, "items": list(await cursor.fetchall())} async def get_system_resources() -> dict[str, Any]: total, used, _free = shutil.disk_usage(".") disk_percent = used / total * 100 if total else 0 pool = get_db_pool() async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor: await cursor.execute( """SELECT (SELECT COUNT(*) FROM sessions WHERE expires_at > CURRENT_TIMESTAMP) AS active_user_count, (SELECT COUNT(*) FROM projects WHERE deleted_at IS NULL) AS active_project_count""" ) stats = await cursor.fetchone() current = { "cpu_usage_percent": None, "memory_usage_percent": None, "disk_usage_percent": round(disk_percent, 2), "active_user_count": stats["active_user_count"], "active_project_count": stats["active_project_count"], "total_storage_mb": round(used / 1024 / 1024, 2), } await cursor.execute( """SELECT timestamp, cpu_usage_percent, memory_usage_percent, disk_usage_percent, active_user_count, active_project_count, total_storage_mb FROM system_resources WHERE timestamp >= %s ORDER BY timestamp""", (datetime.utcnow() - timedelta(hours=24),), ) return {"current": current, "history": list(await cursor.fetchall()), "stats": current}