1
This commit is contained in:
@@ -18,6 +18,10 @@ export interface ProjectItem {
|
||||
id: string;
|
||||
name: string;
|
||||
region?: string | null;
|
||||
road_type?: string | null;
|
||||
project_year?: number | null;
|
||||
estimated_length_m?: number | null;
|
||||
memo?: string | null;
|
||||
status?: string | null;
|
||||
owner_name?: string | null;
|
||||
workflow_stage: number;
|
||||
@@ -92,6 +96,18 @@ export interface ResourceData {
|
||||
stats: ResourceSnapshot;
|
||||
}
|
||||
|
||||
export interface AutomationItem {
|
||||
id: number;
|
||||
project_id: string;
|
||||
name: string;
|
||||
logic_type: string;
|
||||
config_json: Record<string, unknown>;
|
||||
status: "DRAFT" | "ACTIVE" | "INACTIVE";
|
||||
last_executed_at?: string | null;
|
||||
created_at?: string | null;
|
||||
updated_at?: string | null;
|
||||
}
|
||||
|
||||
export interface UpdateUserRequest {
|
||||
name: string;
|
||||
position?: string | null;
|
||||
@@ -99,6 +115,27 @@ export interface UpdateUserRequest {
|
||||
phone?: string | null;
|
||||
}
|
||||
|
||||
export interface UpdateProjectRequest {
|
||||
name: string;
|
||||
region?: string | null;
|
||||
road_type?: string | null;
|
||||
project_year?: number | null;
|
||||
estimated_length_m?: number | null;
|
||||
memo?: string | null;
|
||||
status?: string | null;
|
||||
}
|
||||
|
||||
export interface AdminUpdateUserRequest extends UpdateUserRequest {
|
||||
status?: string | null;
|
||||
}
|
||||
|
||||
export interface AutomationRequest {
|
||||
name: string;
|
||||
logic_type: string;
|
||||
config_json: Record<string, unknown>;
|
||||
status: "DRAFT" | "ACTIVE" | "INACTIVE";
|
||||
}
|
||||
|
||||
export interface CreateCompanyRequest {
|
||||
name: string;
|
||||
business_registration_number: string;
|
||||
@@ -212,7 +249,6 @@ export async function fetchAllProjects(): Promise<ProjectItem[]> {
|
||||
return data.projects;
|
||||
}
|
||||
|
||||
|
||||
export async function fetchAllCompanies(): Promise<CompanyInfo[]> {
|
||||
const data = await request<{ companies: CompanyInfo[] }>("/dashboard/admin/companies");
|
||||
return data.companies;
|
||||
@@ -230,6 +266,27 @@ export function changeUserRole(userId: number, role: string): Promise<unknown> {
|
||||
});
|
||||
}
|
||||
|
||||
export function updateProject(projectId: string, payload: UpdateProjectRequest): Promise<unknown> {
|
||||
return request(`/dashboard/projects/${encodeURIComponent(projectId)}`, {
|
||||
method: "PUT",
|
||||
body: body(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export function deleteProject(projectId: string): Promise<unknown> {
|
||||
return request(`/dashboard/projects/${encodeURIComponent(projectId)}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export function updateDashboardUser(
|
||||
userId: number,
|
||||
payload: AdminUpdateUserRequest,
|
||||
): Promise<unknown> {
|
||||
return request(`/dashboard/admin/users/${userId}`, {
|
||||
method: "PUT",
|
||||
body: body(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export function assignUserToCompany(userId: number, companyId: number | null): Promise<unknown> {
|
||||
return request(`/dashboard/admin/users/${userId}/company`, {
|
||||
method: "PATCH",
|
||||
@@ -254,3 +311,35 @@ export async function fetchAuditLogs(): Promise<AuditLog[]> {
|
||||
export async function fetchSystemResources(days = 30): Promise<ResourceData> {
|
||||
return request<ResourceData>(`/dashboard/admin/resources?days=${encodeURIComponent(days)}`);
|
||||
}
|
||||
|
||||
export async function fetchProjectAutomations(projectId: string): Promise<AutomationItem[]> {
|
||||
const data = await request<{ automations: AutomationItem[] }>(
|
||||
`/dashboard/projects/${encodeURIComponent(projectId)}/automations`,
|
||||
);
|
||||
return data.automations;
|
||||
}
|
||||
|
||||
export function createProjectAutomation(
|
||||
projectId: string,
|
||||
payload: AutomationRequest,
|
||||
): Promise<unknown> {
|
||||
return request(`/dashboard/projects/${encodeURIComponent(projectId)}/automations`, {
|
||||
method: "POST",
|
||||
body: body(payload),
|
||||
});
|
||||
}
|
||||
|
||||
export function updateProjectAutomation(
|
||||
automationId: number,
|
||||
payload: AutomationRequest,
|
||||
): Promise<unknown> {
|
||||
return request(`/dashboard/automations/${automationId}`, { method: "PUT", body: body(payload) });
|
||||
}
|
||||
|
||||
export function deleteProjectAutomation(automationId: number): Promise<unknown> {
|
||||
return request(`/dashboard/automations/${automationId}`, { method: "DELETE" });
|
||||
}
|
||||
|
||||
export function executeProjectAutomation(automationId: number): Promise<unknown> {
|
||||
return request(`/dashboard/automations/${automationId}/execute`, { method: "POST" });
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import shutil
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Any
|
||||
@@ -116,6 +117,69 @@ async def list_all_projects() -> list[dict[str, Any]]:
|
||||
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
|
||||
@@ -358,6 +422,55 @@ async def change_user_role(user_id: int, role: str) -> bool:
|
||||
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()
|
||||
@@ -442,3 +555,114 @@ async def get_system_resources(days: int = 30) -> dict[str, Any]:
|
||||
(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
|
||||
|
||||
@@ -10,9 +10,15 @@ from .B01_Dashboard_Repository import (
|
||||
add_company_member,
|
||||
assign_user_company,
|
||||
change_user_role,
|
||||
count_company_admins,
|
||||
create_company,
|
||||
create_project_automation,
|
||||
delete_project_automation,
|
||||
get_dashboard_me,
|
||||
get_project,
|
||||
get_project_automation,
|
||||
get_system_resources,
|
||||
get_user_admin_target,
|
||||
get_user_company,
|
||||
join_company,
|
||||
list_all_companies,
|
||||
@@ -22,19 +28,28 @@ from .B01_Dashboard_Repository import (
|
||||
list_company_members,
|
||||
list_company_projects,
|
||||
list_join_requests,
|
||||
list_project_automations,
|
||||
list_user_projects,
|
||||
mark_project_automation_executed,
|
||||
process_join_request,
|
||||
remove_company_member,
|
||||
search_companies,
|
||||
soft_delete_project,
|
||||
update_admin_user,
|
||||
update_project,
|
||||
update_project_automation,
|
||||
update_user_profile,
|
||||
)
|
||||
from .B01_Dashboard_Schema import (
|
||||
AddMemberRequest,
|
||||
AdminUpdateUserRequest,
|
||||
AssignCompanyRequest,
|
||||
AutomationRequest,
|
||||
ChangeUserRoleRequest,
|
||||
CreateCompanyRequest,
|
||||
JoinCompanyRequest,
|
||||
ProcessJoinRequest,
|
||||
UpdateProjectRequest,
|
||||
UpdateUserRequest,
|
||||
)
|
||||
|
||||
@@ -56,6 +71,30 @@ def _require_company_id(session: dict[str, Any]) -> int:
|
||||
return int(company_id)
|
||||
|
||||
|
||||
def _same_company(session: dict[str, Any], company_id: int | None) -> bool:
|
||||
return company_id is not None and int(session.get("company_id") or 0) == int(company_id)
|
||||
|
||||
|
||||
def _can_edit_project(session: dict[str, Any], project: dict[str, Any]) -> bool:
|
||||
if session["role"] == "SYSTEM_ADMIN":
|
||||
return True
|
||||
return _same_company(session, project.get("company_id"))
|
||||
|
||||
|
||||
def _can_manage_automation(session: dict[str, Any], project: dict[str, Any]) -> bool:
|
||||
if session["role"] == "SYSTEM_ADMIN":
|
||||
return True
|
||||
return session["role"] == "ADMIN" and _same_company(session, project.get("company_id"))
|
||||
|
||||
|
||||
def _can_edit_user(session: dict[str, Any], target: dict[str, Any]) -> bool:
|
||||
if session["role"] == "SYSTEM_ADMIN":
|
||||
return True
|
||||
if session["role"] == "ADMIN":
|
||||
return _same_company(session, target.get("company_id"))
|
||||
return int(session["user_id"]) == int(target["id"])
|
||||
|
||||
|
||||
@router.get("/me")
|
||||
async def dashboard_me(session: dict[str, Any] = Depends(verify_session)):
|
||||
user = await get_dashboard_me(int(session["user_id"]))
|
||||
@@ -149,11 +188,12 @@ async def admin_process_join_request(
|
||||
payload: ProcessJoinRequest,
|
||||
session: dict[str, Any] = Depends(require_company_admin),
|
||||
):
|
||||
company_id = None if session["role"] == "SYSTEM_ADMIN" else _require_company_id(session)
|
||||
changed = await process_join_request(
|
||||
request_id,
|
||||
int(session["user_id"]),
|
||||
payload.action == "APPROVE",
|
||||
_require_company_id(session),
|
||||
company_id,
|
||||
)
|
||||
if not changed:
|
||||
raise HTTPException(status_code=404, detail="처리할 가입 신청을 찾을 수 없습니다.")
|
||||
@@ -168,6 +208,32 @@ async def admin_projects(session: dict[str, Any] = Depends(require_company_admin
|
||||
}
|
||||
|
||||
|
||||
@router.put("/projects/{project_id}")
|
||||
async def dashboard_update_project(
|
||||
project_id: str,
|
||||
payload: UpdateProjectRequest,
|
||||
session: dict[str, Any] = Depends(verify_session),
|
||||
):
|
||||
project = await get_project(project_id)
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="프로젝트를 찾을 수 없습니다.")
|
||||
if not _can_edit_project(session, project):
|
||||
raise HTTPException(status_code=403, detail="프로젝트 수정 권한이 없습니다.")
|
||||
if not await update_project(project_id, payload.model_dump(), int(session["user_id"])):
|
||||
raise HTTPException(status_code=404, detail="프로젝트를 찾을 수 없습니다.")
|
||||
return {"status": "success"}
|
||||
|
||||
|
||||
@router.delete("/projects/{project_id}")
|
||||
async def dashboard_delete_project(
|
||||
project_id: str,
|
||||
session: dict[str, Any] = Depends(require_system_admin),
|
||||
):
|
||||
if not await soft_delete_project(project_id, int(session["user_id"])):
|
||||
raise HTTPException(status_code=404, detail="프로젝트를 찾을 수 없습니다.")
|
||||
return {"status": "success"}
|
||||
|
||||
|
||||
@router.get("/admin/companies")
|
||||
async def system_companies(session: dict[str, Any] = Depends(require_system_admin)):
|
||||
_ = session
|
||||
@@ -184,14 +250,50 @@ async def system_users(session: dict[str, Any] = Depends(require_system_admin)):
|
||||
async def system_change_role(
|
||||
user_id: int,
|
||||
payload: ChangeUserRoleRequest,
|
||||
session: dict[str, Any] = Depends(require_system_admin),
|
||||
session: dict[str, Any] = Depends(verify_session),
|
||||
):
|
||||
_ = session
|
||||
target = await get_user_admin_target(user_id)
|
||||
if not target:
|
||||
raise HTTPException(status_code=404, detail="사용자를 찾을 수 없습니다.")
|
||||
if payload.role == "SYSTEM_ADMIN" or target["role"] == "SYSTEM_ADMIN":
|
||||
raise HTTPException(
|
||||
status_code=403, detail="시스템 관리자 역할은 API에서 변경할 수 없습니다."
|
||||
)
|
||||
if session["role"] == "ADMIN":
|
||||
if not _same_company(session, target.get("company_id")):
|
||||
raise HTTPException(status_code=403, detail="같은 회사 사용자만 변경할 수 있습니다.")
|
||||
if target["role"] == "ADMIN" and payload.role == "USER":
|
||||
admins = await count_company_admins(int(target["company_id"]))
|
||||
if admins <= 1:
|
||||
raise HTTPException(
|
||||
status_code=409, detail="회사의 마지막 관리자는 변경할 수 없습니다."
|
||||
)
|
||||
elif session["role"] != "SYSTEM_ADMIN":
|
||||
raise HTTPException(status_code=403, detail="역할 변경 권한이 없습니다.")
|
||||
if not await change_user_role(user_id, payload.role):
|
||||
raise HTTPException(status_code=404, detail="사용자를 찾을 수 없습니다.")
|
||||
return {"status": "success"}
|
||||
|
||||
|
||||
@router.put("/admin/users/{user_id}")
|
||||
async def admin_update_user(
|
||||
user_id: int,
|
||||
payload: AdminUpdateUserRequest,
|
||||
session: dict[str, Any] = Depends(verify_session),
|
||||
):
|
||||
target = await get_user_admin_target(user_id)
|
||||
if not target:
|
||||
raise HTTPException(status_code=404, detail="사용자를 찾을 수 없습니다.")
|
||||
if not _can_edit_user(session, target):
|
||||
raise HTTPException(status_code=403, detail="사용자 수정 권한이 없습니다.")
|
||||
data = payload.model_dump()
|
||||
if session["role"] == "USER":
|
||||
data["status"] = None
|
||||
if not await update_admin_user(user_id, data):
|
||||
raise HTTPException(status_code=404, detail="사용자를 찾을 수 없습니다.")
|
||||
return {"status": "success"}
|
||||
|
||||
|
||||
@router.patch("/admin/users/{user_id}/company")
|
||||
async def system_assign_company(
|
||||
user_id: int,
|
||||
@@ -244,3 +346,80 @@ async def system_projects(session: dict[str, Any] = Depends(require_system_admin
|
||||
_ = session
|
||||
return {"status": "success", "projects": await list_all_projects()}
|
||||
|
||||
|
||||
@router.get("/projects/{project_id}/automations")
|
||||
async def dashboard_project_automations(
|
||||
project_id: str,
|
||||
session: dict[str, Any] = Depends(verify_session),
|
||||
):
|
||||
project = await get_project(project_id)
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="프로젝트를 찾을 수 없습니다.")
|
||||
if not _can_edit_project(session, project):
|
||||
raise HTTPException(status_code=403, detail="자동화 로직 조회 권한이 없습니다.")
|
||||
return {"status": "success", "automations": await list_project_automations(project_id)}
|
||||
|
||||
|
||||
@router.post("/projects/{project_id}/automations")
|
||||
async def dashboard_create_automation(
|
||||
project_id: str,
|
||||
payload: AutomationRequest,
|
||||
session: dict[str, Any] = Depends(verify_session),
|
||||
):
|
||||
project = await get_project(project_id)
|
||||
if not project:
|
||||
raise HTTPException(status_code=404, detail="프로젝트를 찾을 수 없습니다.")
|
||||
if not _can_manage_automation(session, project):
|
||||
raise HTTPException(status_code=403, detail="자동화 로직 생성 권한이 없습니다.")
|
||||
automation = await create_project_automation(
|
||||
project_id, int(session["user_id"]), payload.model_dump()
|
||||
)
|
||||
return {"status": "success", "automation": automation}
|
||||
|
||||
|
||||
@router.put("/automations/{automation_id}")
|
||||
async def dashboard_update_automation(
|
||||
automation_id: int,
|
||||
payload: AutomationRequest,
|
||||
session: dict[str, Any] = Depends(verify_session),
|
||||
):
|
||||
automation = await get_project_automation(automation_id)
|
||||
if not automation:
|
||||
raise HTTPException(status_code=404, detail="자동화 로직을 찾을 수 없습니다.")
|
||||
if not _can_manage_automation(session, automation):
|
||||
raise HTTPException(status_code=403, detail="자동화 로직 수정 권한이 없습니다.")
|
||||
if not await update_project_automation(
|
||||
automation_id, int(session["user_id"]), payload.model_dump()
|
||||
):
|
||||
raise HTTPException(status_code=404, detail="자동화 로직을 찾을 수 없습니다.")
|
||||
return {"status": "success"}
|
||||
|
||||
|
||||
@router.delete("/automations/{automation_id}")
|
||||
async def dashboard_delete_automation(
|
||||
automation_id: int,
|
||||
session: dict[str, Any] = Depends(verify_session),
|
||||
):
|
||||
automation = await get_project_automation(automation_id)
|
||||
if not automation:
|
||||
raise HTTPException(status_code=404, detail="자동화 로직을 찾을 수 없습니다.")
|
||||
if not _can_manage_automation(session, automation):
|
||||
raise HTTPException(status_code=403, detail="자동화 로직 삭제 권한이 없습니다.")
|
||||
if not await delete_project_automation(automation_id, int(session["user_id"])):
|
||||
raise HTTPException(status_code=404, detail="자동화 로직을 찾을 수 없습니다.")
|
||||
return {"status": "success"}
|
||||
|
||||
|
||||
@router.post("/automations/{automation_id}/execute")
|
||||
async def dashboard_execute_automation(
|
||||
automation_id: int,
|
||||
session: dict[str, Any] = Depends(verify_session),
|
||||
):
|
||||
automation = await get_project_automation(automation_id)
|
||||
if not automation:
|
||||
raise HTTPException(status_code=404, detail="자동화 로직을 찾을 수 없습니다.")
|
||||
if not _can_manage_automation(session, automation):
|
||||
raise HTTPException(status_code=403, detail="자동화 로직 실행 권한이 없습니다.")
|
||||
if not await mark_project_automation_executed(automation_id, int(session["user_id"])):
|
||||
raise HTTPException(status_code=404, detail="자동화 로직을 찾을 수 없습니다.")
|
||||
return {"status": "success"}
|
||||
|
||||
@@ -36,3 +36,26 @@ class ChangeUserRoleRequest(BaseModel):
|
||||
|
||||
class AssignCompanyRequest(BaseModel):
|
||||
company_id: int | None = Field(default=None, gt=0)
|
||||
|
||||
|
||||
class UpdateProjectRequest(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=255)
|
||||
region: str | None = Field(default=None, max_length=100)
|
||||
road_type: str | None = Field(default=None, max_length=100)
|
||||
project_year: int | None = Field(default=None, ge=1900, le=2100)
|
||||
estimated_length_m: float | None = Field(default=None, ge=0)
|
||||
memo: str | None = Field(default=None, max_length=5000)
|
||||
status: str | None = Field(default=None, max_length=50)
|
||||
|
||||
|
||||
class AdminUpdateUserRequest(UpdateUserRequest):
|
||||
status: str | None = Field(
|
||||
default=None, pattern="^(NO_COMPANY|PENDING|ACTIVE|INACTIVE|REJECTED)$"
|
||||
)
|
||||
|
||||
|
||||
class AutomationRequest(BaseModel):
|
||||
name: str = Field(min_length=1, max_length=100)
|
||||
logic_type: str = Field(min_length=1, max_length=50)
|
||||
config_json: dict = Field(default_factory=dict)
|
||||
status: str = Field(default="DRAFT", pattern="^(DRAFT|ACTIVE|INACTIVE)$")
|
||||
|
||||
Reference in New Issue
Block a user