Files
momentry_core/src/core/tkg/log.rs
T
Accusys bb606f52f5 feat: independent TKG processing mechanism with operation logging
- Created src/core/tkg/ module with service, log, and models
- Added tkg_operation_log table for tracking all TKG operations
- TkgService: build, rebuild (with force option), delete, get_operations
- Updated rebuild endpoint with force parameter
- Added GET /api/v1/file/:file_uuid/tkg for operation history
- Added DELETE /api/v1/file/:file_uuid/tkg for TKG deletion
- TKG rebuild now always triggers Rule 2 (even with 0 edges)
- Full audit trail for all TKG operations (create/update/delete/rebuild)
2026-07-07 03:15:59 +08:00

120 lines
4.1 KiB
Rust

use crate::core::db::schema;
use crate::core::db::PostgresDb;
use anyhow::Result;
use sqlx::PgPool;
pub struct TkgLogger {
pool: PgPool,
pub log_id: Option<i64>,
}
impl TkgLogger {
pub fn new(pool: PgPool) -> Self {
Self { pool, log_id: None }
}
pub async fn start_operation(pool: &PgPool, file_uuid: &str, operation: &str) -> Result<i64> {
let table = schema::table_name("tkg_operation_log");
let id = sqlx::query_scalar::<_, i64>(&format!(
"INSERT INTO {table} (file_uuid, operation, status, started_at) \
VALUES ($1, $2, 'pending', NOW()) RETURNING id"
))
.bind(file_uuid)
.bind(operation)
.fetch_one(pool)
.await?;
tracing::info!("[TKG-Log] Started operation {} for {}", operation, file_uuid);
Ok(id)
}
pub async fn update_progress(
&self,
node_type: &str,
nodes_created: i32,
edges_created: i32,
) -> Result<()> {
if let Some(log_id) = self.log_id {
let table = schema::table_name("tkg_operation_log");
sqlx::query(&format!(
"UPDATE {table} \
SET nodes_created = nodes_created + $1, \
edges_created = edges_created + $2, \
node_type = COALESCE(node_type, $3) \
WHERE id = $4"
))
.bind(nodes_created)
.bind(edges_created)
.bind(node_type)
.bind(log_id)
.execute(&self.pool)
.await?;
}
Ok(())
}
pub async fn complete_operation(&self, error: Option<&str>) -> Result<()> {
if let Some(log_id) = self.log_id {
let table = schema::table_name("tkg_operation_log");
let status = if error.is_some() { "failed" } else { "completed" };
sqlx::query(&format!(
"UPDATE {table} \
SET status = $1, \
error_message = $2, \
completed_at = NOW() \
WHERE id = $3"
))
.bind(status)
.bind(error)
.bind(log_id)
.execute(&self.pool)
.await?;
tracing::info!("[TKG-Log] Operation {} completed with status: {}", log_id, status);
}
Ok(())
}
pub async fn get_operations(pool: &PgPool, file_uuid: &str) -> Result<Vec<crate::core::tkg::models::TkgOperationLog>> {
let table = schema::table_name("tkg_operation_log");
let rows = sqlx::query_as::<_, (i64, String, String, Option<String>, Option<String>, i32, i32, i32, i32, i32, i32, String, Option<String>, String, Option<String>, Option<serde_json::Value>)>(&format!(
"SELECT id, file_uuid, operation, node_type, edge_type, \
nodes_created, nodes_updated, nodes_deleted, \
edges_created, edges_updated, edges_deleted, \
status, error_message, started_at::text, completed_at::text, properties \
FROM {table} WHERE file_uuid = $1 ORDER BY started_at DESC"
))
.bind(file_uuid)
.fetch_all(pool)
.await?;
Ok(rows.into_iter().map(|r| crate::core::tkg::models::TkgOperationLog {
id: r.0,
file_uuid: r.1,
operation: r.2,
node_type: r.3,
edge_type: r.4,
nodes_created: r.5,
nodes_updated: r.6,
nodes_deleted: r.7,
edges_created: r.8,
edges_updated: r.9,
edges_deleted: r.10,
status: r.11,
error_message: r.12,
started_at: r.13,
completed_at: r.14,
properties: r.15,
}).collect())
}
pub async fn delete_operations(pool: &PgPool, file_uuid: &str) -> Result<i64> {
let table = schema::table_name("tkg_operation_log");
let result = sqlx::query(&format!(
"DELETE FROM {table} WHERE file_uuid = $1"
))
.bind(file_uuid)
.execute(pool)
.await?;
Ok(result.rows_affected() as i64)
}
}