Skip to content

Commit 2446c22

Browse files
committed
backup and batching
1 parent c5eff17 commit 2446c22

File tree

5 files changed

+177
-138
lines changed

5 files changed

+177
-138
lines changed

usr-backend/src/backup.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
use std::{process::Command, sync::atomic::Ordering, time::Duration};
2+
3+
use crate::UsrState;
4+
5+
pub fn backup_db(state: &'static UsrState) {
6+
if state.backup_task_running.swap(true, Ordering::Relaxed) {
7+
return;
8+
}
9+
tokio::spawn(async move {
10+
tokio::time::sleep(Duration::from_secs(60 * 10)).await;
11+
state.backup_task_running.store(false, Ordering::Relaxed);
12+
if let Err(e) = std::fs::copy("usr-db.sqlite", "../usr-db-backup/usr-db.sqlite") {
13+
tracing::error!("Failed to copy database: {}", e);
14+
return;
15+
}
16+
if let Err(e) = Command::new("git")
17+
.arg("add")
18+
.arg("usr-db.sqlite")
19+
.current_dir("../usr-db-backup")
20+
.output()
21+
{
22+
tracing::error!("Failed to add files to git: {}", e);
23+
}
24+
if let Err(e) = Command::new("git")
25+
.arg("commit")
26+
.arg("-m")
27+
.arg("Automated backup")
28+
.current_dir("../usr-db-backup")
29+
.output()
30+
{
31+
tracing::error!("Failed to commit files to git: {}", e);
32+
}
33+
if let Err(e) = Command::new("git")
34+
.arg("push")
35+
.current_dir("../usr-db-backup")
36+
.output()
37+
{
38+
tracing::error!("Failed to push files to git: {}", e);
39+
}
40+
});
41+
42+
}

usr-backend/src/main.rs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use std::{
2-
backtrace::Backtrace, io::{LineWriter, Write}, net::SocketAddr, panic::set_hook, path::Path, sync::Arc
2+
backtrace::Backtrace, io::{LineWriter, Write}, net::SocketAddr, panic::set_hook, path::Path, sync::atomic::AtomicBool
33
};
44

55
use axum::{routing::get, Router};
@@ -12,9 +12,12 @@ use tower::ServiceBuilder;
1212
use tower_http::cors::Any;
1313
use tracing::{error, info};
1414
use tracing_subscriber::FmtSubscriber;
15+
use webhook::BatchedWebhook;
1516

1617
mod scheduler;
1718
mod manifest;
19+
mod webhook;
20+
mod backup;
1821

1922
struct LogWriter {
2023
inner: &'static Mutex<LineWriter<std::fs::File>>,
@@ -42,8 +45,9 @@ struct Config {
4245

4346
struct UsrState {
4447
db: DatabaseConnection,
45-
new_orders_webhook: Option<DiscordWebhook>,
46-
order_updates_webhook: Option<DiscordWebhook>,
48+
new_orders_webhook: Option<BatchedWebhook>,
49+
order_updates_webhook: Option<BatchedWebhook>,
50+
backup_task_running: AtomicBool
4751
}
4852

4953
#[tokio::main]
@@ -140,23 +144,24 @@ async fn main() -> anyhow::Result<()> {
140144
})
141145
.layer(tower_http::compression::CompressionLayer::new())
142146
)
143-
.with_state(Arc::new(UsrState {
147+
.with_state(Box::leak(Box::new(UsrState {
144148
db,
145149
new_orders_webhook: {
146150
if let Some(new_orders_webhook) = config.new_orders_webhook {
147-
Some(DiscordWebhook::new(new_orders_webhook)?)
151+
Some(DiscordWebhook::new(new_orders_webhook)?.into())
148152
} else {
149153
None
150154
}
151155
},
152156
order_updates_webhook: {
153157
if let Some(order_updates_webhook) = config.order_updates_webhook {
154-
Some(DiscordWebhook::new(order_updates_webhook)?)
158+
Some(DiscordWebhook::new(order_updates_webhook)?.into())
155159
} else {
156160
None
157161
}
158162
},
159-
}));
163+
backup_task_running: AtomicBool::new(false),
164+
})));
160165

161166
default_provider()
162167
.install_default()

usr-backend/src/manifest.rs

Lines changed: 32 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,17 @@
1-
use std::{collections::{hash_map::Entry, HashMap}, sync::{Arc, LazyLock}, time::Instant}
2-
;
3-
41
use axum::{
52
extract::State, http::StatusCode, response::{IntoResponse, Response}, routing::{delete, get, post}, Json, Router
63
};
7-
use discord_webhook2::message::Message;
8-
use parking_lot::Mutex;
94
use sea_orm::{
105
prelude::Decimal, sea_query::Table, sqlx::types::chrono::Local, ActiveModelTrait, ActiveValue, ColumnTrait, ConnectionTrait, DatabaseConnection, EntityTrait, QueryFilter, QueryOrder, Schema, TransactionTrait
116
};
127
use serde::Deserialize;
138
use tracing::error;
149

15-
use crate::{scheduler, UsrState};
10+
use crate::{backup::backup_db, scheduler, UsrState};
1611

1712
mod order;
1813
mod order_status;
1914

20-
struct BatchedTask {
21-
queue: HashMap<u32, String>,
22-
deadline: Option<Instant>,
23-
}
24-
static BATCHED: LazyLock<Mutex<BatchedTask>> = LazyLock::new(|| Mutex::new(BatchedTask {
25-
queue: HashMap::new(),
26-
deadline: None,
27-
}));
28-
2915
#[derive(Deserialize)]
3016
pub struct PendingOrder {
3117
pub name: String,
@@ -40,7 +26,7 @@ pub struct PendingOrder {
4026

4127
#[axum::debug_handler]
4228
async fn new_order(
43-
State(state): State<Arc<UsrState>>,
29+
State(state): State<&'static UsrState>,
4430
Json(pending_order): Json<PendingOrder>,
4531
) -> (StatusCode, &'static str) {
4632
let webhook_msg = format!(
@@ -82,59 +68,8 @@ async fn new_order(
8268

8369
match result {
8470
Ok(m) => {
85-
let mut guard = BATCHED.lock();
86-
guard.queue.insert(m.id, webhook_msg);
87-
let was_none = guard.deadline.is_none();
88-
guard.deadline = Some(Instant::now() + std::time::Duration::from_secs(60 * 5));
89-
90-
if was_none {
91-
drop(guard);
92-
if state.new_orders_webhook.is_some() {
93-
tokio::spawn(async move {
94-
let new_orders_webhook = state.new_orders_webhook.as_ref().unwrap();
95-
loop {
96-
let deadline = BATCHED.lock().deadline.unwrap();
97-
tokio::time::sleep_until(deadline.into()).await;
98-
let queue;
99-
{
100-
let mut guard = BATCHED.lock();
101-
if guard.deadline.unwrap() != deadline {
102-
continue;
103-
}
104-
let replacement = HashMap::with_capacity(guard.queue.capacity());
105-
queue = std::mem::replace(&mut guard.queue, replacement);
106-
}
107-
let mut running = String::new();
108-
for (_, msg) in queue {
109-
if running.len() + msg.len() + 1 < 2000 {
110-
running.push_str(&msg);
111-
running.push_str("\n");
112-
} else {
113-
if let Err(e) = new_orders_webhook
114-
.send(&Message::new(|message| message.content(running)))
115-
.await
116-
{
117-
error!("Failed to trigger new-order webhook: {e}");
118-
}
119-
running = msg;
120-
}
121-
}
122-
if let Err(e) = new_orders_webhook
123-
.send(&Message::new(|message| message.content(running)))
124-
.await
125-
{
126-
error!("Failed to trigger new-order webhook: {e}");
127-
}
128-
let mut guard = BATCHED.lock();
129-
if guard.queue.is_empty() {
130-
guard.deadline = None;
131-
break;
132-
}
133-
}
134-
});
135-
}
136-
}
137-
71+
backup_db(state);
72+
state.new_orders_webhook.as_ref().map(|x| x.enqueue(m.id, webhook_msg));
13873
(StatusCode::OK, "")
13974
}
14075
Err(e) => {
@@ -159,7 +94,7 @@ pub struct ChangeOrder {
15994

16095
#[axum::debug_handler]
16196
async fn change_order(
162-
State(state): State<Arc<UsrState>>,
97+
State(state): State<&'static UsrState>,
16398
Json(change_order): Json<ChangeOrder>,
16499
) -> (StatusCode, &'static str) {
165100
match order_status::Entity::find().filter(order_status::Column::OrderId.eq(change_order.id)).order_by_desc(order_status::Column::InstanceId).one(&state.db).await {
@@ -202,23 +137,25 @@ async fn change_order(
202137
error!("Failed to change order: {e}");
203138
(StatusCode::INTERNAL_SERVER_ERROR, "")
204139
} else {
205-
let mut guard = BATCHED.lock();
206-
match guard.queue.entry(change_order.id) {
207-
Entry::Occupied(mut entry) => {
208-
entry.insert(webhook_msg);
209-
}
210-
Entry::Vacant(_) => {
211-
tokio::spawn(async move {
212-
let Some(new_orders_webhook) = state.new_orders_webhook.as_ref() else { return; };
213-
if let Err(e) = new_orders_webhook
214-
.send(&Message::new(|message| message.content(webhook_msg)))
215-
.await
216-
{
217-
error!("Failed to trigger new-order webhook: {e}");
218-
}
219-
});
220-
}
221-
}
140+
backup_db(state);
141+
state.new_orders_webhook.as_ref().map(|x| x.enqueue(change_order.id, webhook_msg));
142+
// let mut guard = BATCHED.lock();
143+
// match guard.queue.entry(change_order.id) {
144+
// Entry::Occupied(mut entry) => {
145+
// entry.insert(webhook_msg);
146+
// }
147+
// Entry::Vacant(_) => {
148+
// tokio::spawn(async move {
149+
// let Some(new_orders_webhook) = state.new_orders_webhook.as_ref() else { return; };
150+
// if let Err(e) = new_orders_webhook
151+
// .send(&Message::new(|message| message.content(webhook_msg)))
152+
// .await
153+
// {
154+
// error!("Failed to trigger new-order webhook: {e}");
155+
// }
156+
// });
157+
// }
158+
// }
222159

223160
(StatusCode::OK, "")
224161
}
@@ -233,7 +170,7 @@ struct DeleteOrder {
233170

234171
#[axum::debug_handler]
235172
async fn cancel_order(
236-
State(state): State<Arc<UsrState>>,
173+
State(state): State<&'static UsrState>,
237174
Json(DeleteOrder { id, force }): Json<DeleteOrder>,
238175
) -> (StatusCode, &'static str) {
239176
let webhook_msg;
@@ -284,15 +221,8 @@ async fn cancel_order(
284221
return (StatusCode::INTERNAL_SERVER_ERROR, "");
285222
}
286223

287-
tokio::spawn(async move {
288-
let Some(new_orders_webhook) = state.new_orders_webhook.as_ref() else { return; };
289-
if let Err(e) = new_orders_webhook
290-
.send(&Message::new(|message| message.content(webhook_msg)))
291-
.await
292-
{
293-
error!("Failed to trigger new-order webhook: {e}");
294-
}
295-
});
224+
state.new_orders_webhook.as_ref().map(|x| x.enqueue(id, webhook_msg));
225+
backup_db(state);
296226

297227
(StatusCode::OK, "")
298228
}
@@ -305,7 +235,7 @@ pub struct UpdateOrder {
305235

306236
#[axum::debug_handler]
307237
async fn update_order(
308-
State(state): State<Arc<UsrState>>,
238+
State(state): State<&'static UsrState>,
309239
Json(update_order): Json<UpdateOrder>,
310240
) -> (StatusCode, &'static str) {
311241
let webhook_msg;
@@ -369,22 +299,15 @@ async fn update_order(
369299
error!("Failed to update order status: {e}");
370300
(StatusCode::INTERNAL_SERVER_ERROR, "")
371301
} else {
372-
tokio::spawn(async move {
373-
let Some(order_updates_webhook) = state.order_updates_webhook.as_ref() else { return; };
374-
if let Err(e) = order_updates_webhook
375-
.send(&Message::new(|message| message.content(webhook_msg)))
376-
.await
377-
{
378-
error!("Failed to trigger order-updates webhook: {e}");
379-
}
380-
});
302+
state.order_updates_webhook.as_ref().map(|x| x.enqueue(update_order.id, webhook_msg));
303+
backup_db(state);
381304
(StatusCode::OK, "")
382305
}
383306
}
384307

385308
#[axum::debug_handler]
386309
async fn get_orders(
387-
State(state): State<Arc<UsrState>>,
310+
State(state): State<&'static UsrState>,
388311
) -> Response {
389312
let result = order::Entity::find().all(&state.db).await;
390313

@@ -412,7 +335,7 @@ async fn get_orders(
412335
}
413336
}
414337

415-
pub fn router() -> Router<Arc<UsrState>> {
338+
pub fn router() -> Router<&'static UsrState> {
416339
Router::new()
417340
.route("/new/order", post(new_order))
418341
.route("/change/order", post(change_order))

0 commit comments

Comments
 (0)