Skip to content

Commit 5e2c9f4

Browse files
committed
template engine, activity log
1 parent 58d5884 commit 5e2c9f4

File tree

97 files changed

+6359
-4848
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

97 files changed

+6359
-4848
lines changed

backend/Cargo.lock

Lines changed: 590 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

backend/Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ cookie = "0.18"
3333
time = { version = "0.3", features = ["macros"] }
3434
siwe = "0.6"
3535
hex = "0.4"
36-
ethers = { version = "2.0", default-features = false }
36+
ethers = { version = "2.0", features = ["rustls"] }
3737
hex-literal = "0.4"
3838
utoipa = { version = "4", features = ["axum_extras"] }
3939
utoipa-swagger-ui = { version = "6", features = ["axum"] }
@@ -45,6 +45,8 @@ rand = "0.8"
4545
bs58 = "0.5"
4646
sha2 = "0.10"
4747
regex = "1.10"
48+
serde_yaml = "0.9"
49+
async-trait = "0.1.89"
4850

4951
[profile.release]
5052
opt-level = 3
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
CREATE TABLE IF NOT EXISTS worker_activity (
2+
id TEXT PRIMARY KEY NOT NULL,
3+
user_id TEXT NOT NULL,
4+
monitor_id TEXT,
5+
event_type TEXT NOT NULL,
6+
safe_address TEXT,
7+
network TEXT,
8+
message TEXT NOT NULL,
9+
metadata TEXT,
10+
created_at TEXT NOT NULL,
11+
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
12+
FOREIGN KEY (monitor_id) REFERENCES monitors(id) ON DELETE CASCADE
13+
);
14+
15+
CREATE INDEX IF NOT EXISTS idx_worker_activity_user_id ON worker_activity(user_id);
16+
CREATE INDEX IF NOT EXISTS idx_worker_activity_created_at ON worker_activity(created_at DESC);
17+
CREATE INDEX IF NOT EXISTS idx_worker_activity_event_type ON worker_activity(event_type);

backend/src/api/activity.rs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
use axum::{
2+
extract::{Query, State},
3+
http::StatusCode,
4+
Extension, Json,
5+
};
6+
use serde::{Deserialize, Serialize};
7+
use crate::api::AppState;
8+
use crate::models::worker_activity::WorkerActivity;
9+
10+
#[derive(Debug, Deserialize)]
11+
#[serde(rename_all = "camelCase")]
12+
pub struct ActivityQuery {
13+
pub limit: Option<i32>,
14+
pub event_type: Option<String>,
15+
pub monitor_id: Option<String>,
16+
}
17+
18+
#[derive(Debug, Serialize)]
19+
#[serde(rename_all = "camelCase")]
20+
pub struct ActivityResponse {
21+
pub activities: Vec<WorkerActivity>,
22+
pub total: i64,
23+
}
24+
25+
pub async fn list_activity(
26+
State(state): State<AppState>,
27+
Extension(user_id): Extension<String>,
28+
Query(query): Query<ActivityQuery>,
29+
) -> Result<Json<ActivityResponse>, StatusCode> {
30+
let limit = query.limit.unwrap_or(50).min(100);
31+
32+
let mut sql = String::from(
33+
"SELECT id, user_id, monitor_id, event_type, safe_address, network, message, metadata, created_at
34+
FROM worker_activity
35+
WHERE user_id = ?"
36+
);
37+
38+
let mut params: Vec<String> = vec![user_id.clone()];
39+
40+
if let Some(ref event_type) = query.event_type {
41+
sql.push_str(" AND event_type = ?");
42+
params.push(event_type.clone());
43+
}
44+
45+
if let Some(ref monitor_id) = query.monitor_id {
46+
sql.push_str(" AND monitor_id = ?");
47+
params.push(monitor_id.clone());
48+
}
49+
50+
sql.push_str(" ORDER BY created_at DESC LIMIT ?");
51+
52+
let activities = match (query.event_type.as_ref(), query.monitor_id.as_ref()) {
53+
(Some(event_type), Some(monitor_id)) => {
54+
sqlx::query_as::<_, WorkerActivity>(&sql)
55+
.bind(&user_id)
56+
.bind(event_type)
57+
.bind(monitor_id)
58+
.bind(limit)
59+
.fetch_all(&state.pool)
60+
.await
61+
}
62+
(Some(event_type), None) => {
63+
sqlx::query_as::<_, WorkerActivity>(&sql)
64+
.bind(&user_id)
65+
.bind(event_type)
66+
.bind(limit)
67+
.fetch_all(&state.pool)
68+
.await
69+
}
70+
(None, Some(monitor_id)) => {
71+
sqlx::query_as::<_, WorkerActivity>(&sql)
72+
.bind(&user_id)
73+
.bind(monitor_id)
74+
.bind(limit)
75+
.fetch_all(&state.pool)
76+
.await
77+
}
78+
(None, None) => {
79+
sqlx::query_as::<_, WorkerActivity>(&sql)
80+
.bind(&user_id)
81+
.bind(limit)
82+
.fetch_all(&state.pool)
83+
.await
84+
}
85+
}.map_err(|e| {
86+
tracing::error!("Failed to fetch activity: {}", e);
87+
StatusCode::INTERNAL_SERVER_ERROR
88+
})?;
89+
90+
let total: (i64,) = sqlx::query_as(
91+
"SELECT COUNT(*) FROM worker_activity WHERE user_id = ?"
92+
)
93+
.bind(&user_id)
94+
.fetch_one(&state.pool)
95+
.await
96+
.map_err(|e| {
97+
tracing::error!("Failed to count activity: {}", e);
98+
StatusCode::INTERNAL_SERVER_ERROR
99+
})?;
100+
101+
Ok(Json(ActivityResponse {
102+
activities,
103+
total: total.0,
104+
}))
105+
}
106+
107+
pub async fn clear_activity(
108+
State(state): State<AppState>,
109+
Extension(user_id): Extension<String>,
110+
) -> Result<StatusCode, StatusCode> {
111+
sqlx::query("DELETE FROM worker_activity WHERE user_id = ?")
112+
.bind(&user_id)
113+
.execute(&state.pool)
114+
.await
115+
.map_err(|e| {
116+
tracing::error!("Failed to clear activity: {}", e);
117+
StatusCode::INTERNAL_SERVER_ERROR
118+
})?;
119+
120+
Ok(StatusCode::NO_CONTENT)
121+
}

0 commit comments

Comments
 (0)