136 lines
4.9 KiB
Python
136 lines
4.9 KiB
Python
"""B02_ProjRegister aiomysql Raw SQL 저장소."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
from typing import Any
|
|
from uuid import uuid4
|
|
|
|
import aiomysql
|
|
|
|
from common_util.common_util_json import atomic_write_json
|
|
from common_util.common_util_storage import PROJECT_STORAGE_LAYOUT_V2
|
|
from common_util.common_util_workflow import load_project_workflow
|
|
from common_util.common_util_workflow_state import initialize_project_stages
|
|
from config.config_db import get_db_pool
|
|
from config.config_system import STORAGE_BASE_DIR
|
|
|
|
|
|
def _build_project_storage(company_id: int, user_id: int, project_id: str) -> tuple[str, Path]:
|
|
relative_path = f"storage/{company_id}/{user_id}/{project_id}"
|
|
storage_root = Path(STORAGE_BASE_DIR).resolve()
|
|
project_root = (storage_root / str(company_id) / str(user_id) / project_id).resolve()
|
|
if storage_root not in (project_root, *project_root.parents):
|
|
raise ValueError("프로젝트 저장 경로가 저장소 루트를 벗어났습니다.")
|
|
return relative_path, project_root
|
|
|
|
|
|
def _initialize_project_storage(project_root: Path, project_id: str) -> None:
|
|
for stage, subdir in PROJECT_STORAGE_LAYOUT_V2:
|
|
(project_root / stage / subdir).mkdir(parents=True, exist_ok=True)
|
|
atomic_write_json(project_root / "workflow.json", load_project_workflow(project_root))
|
|
atomic_write_json(
|
|
project_root / "project_manifest.json",
|
|
{
|
|
"project_id": project_id,
|
|
"storage_version": 2,
|
|
"created_at": datetime.utcnow().isoformat(timespec="seconds"),
|
|
"stages": [f"{stage}/{subdir}" for stage, subdir in PROJECT_STORAGE_LAYOUT_V2],
|
|
},
|
|
)
|
|
|
|
|
|
async def create_project(
|
|
*,
|
|
user_id: int,
|
|
company_id: int,
|
|
name: str,
|
|
region: str,
|
|
road_type: str,
|
|
project_year: int,
|
|
estimated_length_m: float | None,
|
|
memo: str | None,
|
|
) -> dict[str, Any]:
|
|
"""신규 프로젝트를 DB에 저장하고 워크플로우 저장소를 초기화한다."""
|
|
|
|
project_id = str(uuid4())
|
|
storage_path, project_root = _build_project_storage(company_id, user_id, project_id)
|
|
now = datetime.utcnow()
|
|
|
|
pool = get_db_pool()
|
|
async with pool.acquire() as connection, connection.cursor(aiomysql.DictCursor) as cursor:
|
|
try:
|
|
await connection.begin()
|
|
await cursor.execute(
|
|
"""
|
|
INSERT INTO projects (
|
|
id, user_id, company_id, name, region, road_type,
|
|
project_year, estimated_length_m, memo, status,
|
|
crs_epsg, storage_path, created_at, updated_at
|
|
)
|
|
VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, 'NEW', 5178, %s, %s, %s)
|
|
""",
|
|
(
|
|
project_id,
|
|
user_id,
|
|
company_id,
|
|
name,
|
|
region,
|
|
road_type,
|
|
project_year,
|
|
estimated_length_m,
|
|
memo,
|
|
storage_path,
|
|
now,
|
|
now,
|
|
),
|
|
)
|
|
# 워크플로우 단계별 상태 초기화 시드
|
|
await initialize_project_stages(cursor, project_id)
|
|
|
|
await cursor.execute(
|
|
"""INSERT INTO system_audit_logs (user_id, action, resource_type, resource_id)
|
|
VALUES (%s, 'PROJECT_CREATE', 'project', NULL)""",
|
|
(user_id,),
|
|
)
|
|
_initialize_project_storage(project_root, project_id)
|
|
await connection.commit()
|
|
except Exception:
|
|
await connection.rollback()
|
|
raise
|
|
|
|
await cursor.execute(
|
|
"""
|
|
SELECT id AS project_id, name, region, road_type, project_year,
|
|
estimated_length_m, memo, status, storage_path,
|
|
DATE_FORMAT(created_at, '%%Y-%%m-%%dT%%H:%%i:%%s') AS created_at
|
|
FROM projects
|
|
WHERE id = %s
|
|
""",
|
|
(project_id,),
|
|
)
|
|
row = await cursor.fetchone()
|
|
|
|
if not row:
|
|
raise RuntimeError("생성된 프로젝트를 다시 조회할 수 없습니다.")
|
|
return dict(row)
|
|
|
|
|
|
async def get_project_by_id(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 AS project_id, user_id, company_id, name, region, road_type,
|
|
project_year, estimated_length_m, memo, status, storage_path,
|
|
DATE_FORMAT(created_at, '%%Y-%%m-%%dT%%H:%%i:%%s') AS created_at
|
|
FROM projects
|
|
WHERE id = %s AND deleted_at IS NULL
|
|
""",
|
|
(project_id,),
|
|
)
|
|
return await cursor.fetchone()
|