Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
parkpow
GitHub Repository: parkpow/deep-license-plate-recognition
Path: blob/master/gate-controller/src-tauri/src/webhook_server.rs
1072 views
1
use tiny_http::{Server, Response, Request, Header, Method};
2
use tauri::{AppHandle, Manager, State, Emitter};
3
use std::io::{Cursor, Read};
4
use serde_json::json;
5
use chrono::Utc;
6
7
use crate::config;
8
use crate::relay::{self, ConnectionManager, RelayActionPayload};
9
10
#[derive(Clone, serde::Serialize)]
11
struct WebhookLog {
12
timestamp: String,
13
success: bool,
14
details: String,
15
error_message: Option<String>,
16
}
17
18
#[derive(serde::Deserialize)]
19
struct WebhookPayload {
20
id: String,
21
action: String,
22
channel: Option<u8>,
23
toggle: Option<bool>,
24
period: Option<u64>,
25
}
26
27
fn handle_request(req: &mut Request, app_handle: &AppHandle) -> Response<Cursor<Vec<u8>>> {
28
let log_and_emit = |success: bool, details: String, error_message: Option<String>| {
29
let log_entry = WebhookLog {
30
timestamp: Utc::now().to_rfc3339(),
31
success,
32
details: details.clone(),
33
error_message,
34
};
35
36
// Emit event to frontend
37
if let Err(e) = app_handle.emit("webhook-log", &log_entry) {
38
eprintln!("Failed to emit event: {}", e);
39
}
40
41
// Append to log file
42
if let Err(e) = config::append_to_log(app_handle, &format!(
43
"{} - Success: {} - Details: {} - Error: {}",
44
log_entry.timestamp,
45
log_entry.success,
46
log_entry.details,
47
log_entry.error_message.as_deref().unwrap_or("N/A")
48
)) {
49
eprintln!("Failed to write to log file: {}", e);
50
}
51
};
52
53
let json_error = |status_code: i32, message: &str, details: String| {
54
log_and_emit(false, details, Some(message.to_string()));
55
let response_json = json!({
56
"status": "error",
57
"message": message
58
});
59
let response_body = serde_json::to_string(&response_json).unwrap_or_default();
60
let mut response = Response::from_string(response_body).with_status_code(status_code);
61
let json_header = Header::from_bytes(b"Content-Type", b"application/json").unwrap();
62
response.add_header(json_header);
63
response
64
};
65
66
// --- Auth Check ---
67
let config = match config::load_config(app_handle) {
68
Ok(c) => c,
69
Err(e) => return json_error(500, "Failed to load configuration", e),
70
};
71
72
let stored_token = match config.webhook_token {
73
Some(token) if !token.is_empty() => token,
74
_ => {
75
return json_error(
76
401,
77
"Unauthorized: Webhook token not configured",
78
"Authentication failed".to_string(),
79
);
80
}
81
};
82
83
let auth_header = req.headers().iter().find(|h| h.field.equiv("Authorization"));
84
if let Some(header) = auth_header {
85
let header_val = header.value.as_str();
86
if header_val.starts_with("Bearer ") {
87
let token = &header_val[7..];
88
if token != stored_token.as_str() {
89
return json_error(
90
401,
91
"Unauthorized: Invalid token",
92
"Authentication failed".to_string(),
93
);
94
}
95
} else {
96
return json_error(
97
401,
98
"Unauthorized: Invalid auth header format",
99
"Authentication failed".to_string(),
100
);
101
}
102
} else {
103
return json_error(
104
401,
105
"Unauthorized: Missing Authorization header",
106
"Authentication failed".to_string(),
107
);
108
}
109
110
// --- POS Health Check ---
111
// Convert the URL to lowercase for case-insensitive checking.
112
if req.url().to_lowercase().contains("?check=true") {
113
log_and_emit(true, "Health check successful".to_string(), None);
114
let response_json = json!({
115
"status": "ok",
116
"message": "Webhook server is running and token is valid."
117
});
118
let response_body = serde_json::to_string(&response_json).unwrap_or_default();
119
let mut response = Response::from_string(response_body).with_status_code(200);
120
let json_header = Header::from_bytes(b"Content-Type", b"application/json").unwrap();
121
response.add_header(json_header);
122
return response;
123
}
124
125
// --- Request Handling ---
126
// Get only the part of the URL before '?' for validation.
127
let path = req.url().split('?').next().unwrap_or("");
128
if req.method() != &Method::Post || path != "/webhook" {
129
return json_error(
130
404,
131
"Not Found: Expected POST /webhook",
132
format!("Invalid request: {} {}", req.method(), req.url()),
133
);
134
}
135
136
let mut content = String::new();
137
if let Err(e) = req.as_reader().read_to_string(&mut content) {
138
return json_error(
139
400,
140
&format!("Bad Request: Failed to read body: {}", e),
141
"Request body error".to_string(),
142
);
143
}
144
145
let payload: WebhookPayload = match serde_json::from_str(&content) {
146
Ok(p) => p,
147
Err(e) => return json_error(400, &format!("Bad Request: Invalid JSON: {}", e), content),
148
};
149
150
let details = format!(
151
"Trigger relay '{}', action '{}', channel {:?}, toggle {:?}, period {:?}",
152
payload.id, payload.action, payload.channel, payload.toggle, payload.period
153
);
154
155
// --- Relay Trigger ---
156
let connections_state: State<ConnectionManager> = app_handle.state();
157
let relay_payload = RelayActionPayload {
158
id: payload.id.clone(),
159
action: payload.action.clone(),
160
channel: payload.channel,
161
toggle: payload.toggle,
162
period: payload.period,
163
};
164
165
match relay::trigger_relay_action(app_handle.clone(), connections_state, relay_payload) {
166
Ok(_) => {
167
log_and_emit(true, details, None);
168
let response_json = json!({
169
"status": "success",
170
"id": payload.id,
171
"action": payload.action
172
});
173
let response_body = serde_json::to_string(&response_json).unwrap_or_default();
174
let mut response = Response::from_string(response_body);
175
let json_header = Header::from_bytes(b"Content-Type", b"application/json").unwrap();
176
response.add_header(json_header);
177
response
178
}
179
Err(e) => json_error(500, &e, details),
180
}
181
}
182
183
pub fn start_server(app_handle: AppHandle) {
184
std::thread::spawn(move || {
185
let server = match Server::http("0.0.0.0:4848") {
186
Ok(s) => s,
187
Err(e) => {
188
eprintln!("Failed to start webhook server: {}", e);
189
return;
190
}
191
};
192
println!("Webhook server started on http://0.0.0.0:4848");
193
194
for mut request in server.incoming_requests() {
195
let response = handle_request(&mut request, &app_handle);
196
if let Err(e) = request.respond(response) {
197
eprintln!("Failed to send response: {}", e);
198
}
199
}
200
});
201
}
202
203