669 lines
26 KiB
Python
669 lines
26 KiB
Python
"""B01_Dashboard aiomysql Raw SQL 저장소."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import shutil
|
|
from datetime import datetime, timedelta
|
|
from typing import Any
|
|
|
|
import aiomysql
|
|
import psutil
|
|
|
|
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 list_all_projects() -> 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.deleted_at IS NULL
|
|
ORDER BY p.updated_at DESC, p.created_at DESC"""
|
|
)
|
|
return [_project_row(row) for row in await cursor.fetchall()]
|
|
|
|
|
|
async def get_project(project_id: str) -> dict[str, Any] | None:
|
|
pool = get_db_pool()
|
|
async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor:
|
|
await cursor.execute(
|
|
"""SELECT id, user_id, company_id, name, region, road_type, project_year,
|
|
estimated_length_m, memo, status
|
|
FROM projects WHERE id = %s AND deleted_at IS NULL""",
|
|
(project_id,),
|
|
)
|
|
return await cursor.fetchone()
|
|
|
|
|
|
async def update_project(project_id: str, data: dict[str, Any], actor_id: int) -> bool:
|
|
pool = get_db_pool()
|
|
async with pool.acquire() as connection, connection.cursor() as cursor:
|
|
await connection.begin()
|
|
await cursor.execute(
|
|
"""UPDATE projects
|
|
SET name = %s, region = %s, road_type = %s, project_year = %s,
|
|
estimated_length_m = %s, memo = %s, status = COALESCE(%s, status)
|
|
WHERE id = %s AND deleted_at IS NULL""",
|
|
(
|
|
data["name"],
|
|
data.get("region"),
|
|
data.get("road_type"),
|
|
data.get("project_year"),
|
|
data.get("estimated_length_m"),
|
|
data.get("memo"),
|
|
data.get("status"),
|
|
project_id,
|
|
),
|
|
)
|
|
changed = cursor.rowcount > 0
|
|
if changed:
|
|
await cursor.execute(
|
|
"""INSERT INTO system_audit_logs (user_id, action, resource_type, resource_id)
|
|
VALUES (%s, 'PROJECT_UPDATE', 'project', NULL)""",
|
|
(actor_id,),
|
|
)
|
|
await connection.commit()
|
|
return changed
|
|
|
|
|
|
async def soft_delete_project(project_id: str, actor_id: int) -> bool:
|
|
pool = get_db_pool()
|
|
async with pool.acquire() as connection, connection.cursor() as cursor:
|
|
await connection.begin()
|
|
await cursor.execute(
|
|
"""UPDATE projects SET deleted_at = CURRENT_TIMESTAMP
|
|
WHERE id = %s AND deleted_at IS NULL""",
|
|
(project_id,),
|
|
)
|
|
changed = cursor.rowcount > 0
|
|
if changed:
|
|
await cursor.execute(
|
|
"""INSERT INTO system_audit_logs (user_id, action, resource_type, resource_id)
|
|
VALUES (%s, 'PROJECT_DELETE', 'project', NULL)""",
|
|
(actor_id,),
|
|
)
|
|
await connection.commit()
|
|
return changed
|
|
|
|
|
|
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 get_user_admin_target(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 id, company_id, role, is_master, status
|
|
FROM users WHERE id = %s AND 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_admin_user(user_id: int, data: dict[str, Any]) -> bool:
|
|
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,
|
|
status = COALESCE(%s, status)
|
|
WHERE id = %s AND deleted_at IS NULL""",
|
|
(
|
|
data["name"],
|
|
data.get("position"),
|
|
data.get("department"),
|
|
data.get("phone"),
|
|
data.get("status"),
|
|
user_id,
|
|
),
|
|
)
|
|
changed = cursor.rowcount > 0
|
|
await connection.commit()
|
|
return changed
|
|
|
|
|
|
async def count_company_admins(company_id: int) -> int:
|
|
pool = get_db_pool()
|
|
async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor:
|
|
await cursor.execute(
|
|
"""SELECT COUNT(*) AS cnt FROM users
|
|
WHERE company_id = %s AND role = 'ADMIN' AND deleted_at IS NULL""",
|
|
(company_id,),
|
|
)
|
|
row = await cursor.fetchone()
|
|
return int(row["cnt"] if row else 0)
|
|
|
|
|
|
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(days: int = 30) -> dict[str, Any]:
|
|
total, used, _free = shutil.disk_usage(".")
|
|
disk_percent = used / total * 100 if total else 0
|
|
cpu_percent = psutil.cpu_percent(interval=0.1)
|
|
memory_percent = psutil.virtual_memory().percent
|
|
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": round(cpu_percent, 2),
|
|
"memory_usage_percent": round(memory_percent, 2),
|
|
"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),
|
|
}
|
|
since = datetime.utcnow() - timedelta(days=days)
|
|
# 다운샘플링: 조회 구간을 최대 TARGET_POINTS개 시간버킷으로 나눠 평균.
|
|
# 계측 간격(2분)보다 버킷이 작으면 원본 그대로(버킷=간격) 반환.
|
|
target_points = 300
|
|
await cursor.execute(
|
|
"SELECT COUNT(*) AS cnt FROM system_resources WHERE timestamp >= %s",
|
|
(since,),
|
|
)
|
|
row_cnt_res = await cursor.fetchone()
|
|
row_count = row_cnt_res["cnt"] if row_cnt_res else 0
|
|
if row_count <= target_points:
|
|
bucket_seconds = 120
|
|
else:
|
|
bucket_seconds = max(120, (days * 86400) // target_points)
|
|
date_fmt = "%%Y-%%m-%%dT%%H:%%i:%%s"
|
|
await cursor.execute(
|
|
f"""SELECT
|
|
DATE_FORMAT(
|
|
FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(timestamp) / %s) * %s),
|
|
'{date_fmt}'
|
|
) AS timestamp,
|
|
ROUND(AVG(cpu_usage_percent), 2) AS cpu_usage_percent,
|
|
ROUND(AVG(memory_usage_percent), 2) AS memory_usage_percent,
|
|
ROUND(AVG(disk_usage_percent), 2) AS disk_usage_percent,
|
|
MAX(active_user_count) AS active_user_count,
|
|
MAX(active_project_count) AS active_project_count,
|
|
ROUND(AVG(total_storage_mb), 2) AS total_storage_mb
|
|
FROM system_resources
|
|
WHERE timestamp >= %s
|
|
GROUP BY FLOOR(UNIX_TIMESTAMP(timestamp) / %s)
|
|
ORDER BY timestamp""",
|
|
(bucket_seconds, bucket_seconds, since, bucket_seconds),
|
|
)
|
|
return {"current": current, "history": list(await cursor.fetchall()), "stats": current}
|
|
|
|
|
|
async def list_project_automations(project_id: str) -> 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, project_id, name, logic_type, config_json, status,
|
|
last_executed_at, created_at, updated_at
|
|
FROM project_automations
|
|
WHERE project_id = %s AND deleted_at IS NULL
|
|
ORDER BY updated_at DESC, created_at DESC""",
|
|
(project_id,),
|
|
)
|
|
return list(await cursor.fetchall())
|
|
|
|
|
|
async def create_project_automation(
|
|
project_id: str, actor_id: int, data: dict[str, Any]
|
|
) -> dict[str, Any]:
|
|
pool = get_db_pool()
|
|
async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor:
|
|
await connection.begin()
|
|
await cursor.execute(
|
|
"""INSERT INTO project_automations
|
|
(project_id, name, logic_type, config_json, status, created_by, updated_by)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s)""",
|
|
(
|
|
project_id,
|
|
data["name"],
|
|
data["logic_type"],
|
|
json.dumps(data["config_json"], ensure_ascii=False),
|
|
data["status"],
|
|
actor_id,
|
|
actor_id,
|
|
),
|
|
)
|
|
automation_id = cursor.lastrowid
|
|
await connection.commit()
|
|
await cursor.execute("SELECT * FROM project_automations WHERE id = %s", (automation_id,))
|
|
return await cursor.fetchone()
|
|
|
|
|
|
async def get_project_automation(automation_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 a.*, p.company_id
|
|
FROM project_automations a
|
|
JOIN projects p ON p.id = a.project_id
|
|
WHERE a.id = %s AND a.deleted_at IS NULL AND p.deleted_at IS NULL""",
|
|
(automation_id,),
|
|
)
|
|
return await cursor.fetchone()
|
|
|
|
|
|
async def update_project_automation(
|
|
automation_id: int, actor_id: int, data: dict[str, Any]
|
|
) -> bool:
|
|
pool = get_db_pool()
|
|
async with pool.acquire() as connection, connection.cursor() as cursor:
|
|
await cursor.execute(
|
|
"""UPDATE project_automations
|
|
SET name = %s, logic_type = %s, config_json = %s, status = %s, updated_by = %s
|
|
WHERE id = %s AND deleted_at IS NULL""",
|
|
(
|
|
data["name"],
|
|
data["logic_type"],
|
|
json.dumps(data["config_json"], ensure_ascii=False),
|
|
data["status"],
|
|
actor_id,
|
|
automation_id,
|
|
),
|
|
)
|
|
changed = cursor.rowcount > 0
|
|
await connection.commit()
|
|
return changed
|
|
|
|
|
|
async def delete_project_automation(automation_id: int, actor_id: int) -> bool:
|
|
pool = get_db_pool()
|
|
async with pool.acquire() as connection, connection.cursor() as cursor:
|
|
await connection.begin()
|
|
await cursor.execute(
|
|
"""UPDATE project_automations
|
|
SET deleted_at = CURRENT_TIMESTAMP, updated_by = %s
|
|
WHERE id = %s AND deleted_at IS NULL""",
|
|
(actor_id, automation_id),
|
|
)
|
|
changed = cursor.rowcount > 0
|
|
if changed:
|
|
await cursor.execute(
|
|
"""INSERT INTO system_audit_logs (user_id, action, resource_type, resource_id)
|
|
VALUES (%s, 'AUTOMATION_DELETE', 'automation', %s)""",
|
|
(actor_id, automation_id),
|
|
)
|
|
await connection.commit()
|
|
return changed
|
|
|
|
|
|
async def mark_project_automation_executed(automation_id: int, actor_id: int) -> bool:
|
|
pool = get_db_pool()
|
|
async with pool.acquire() as connection, connection.cursor() as cursor:
|
|
await cursor.execute(
|
|
"""UPDATE project_automations
|
|
SET last_executed_at = CURRENT_TIMESTAMP, updated_by = %s
|
|
WHERE id = %s AND deleted_at IS NULL""",
|
|
(actor_id, automation_id),
|
|
)
|
|
changed = cursor.rowcount > 0
|
|
await connection.commit()
|
|
return changed
|