lockbook_server_lib/
debug_info.rs1use crate::ServerError::ClientError;
2use std::fmt::Debug;
3use std::ops::DerefMut;
4
5use db_rs::Db;
6use lb_rs::model::{
7 account::BETA_USERS,
8 api::{UpsertDebugInfoError, UpsertDebugInfoRequest},
9 file_metadata::Owner,
10};
11use libsecp256k1::PublicKey;
12use reqwest::multipart;
13use serde_json::json;
14use tracing::{info, warn};
15
16use crate::{
17 RequestContext, ServerError, ServerState,
18 billing::{
19 app_store_client::AppStoreClient, google_play_client::GooglePlayClient,
20 stripe_client::StripeClient,
21 },
22 document_service::DocumentService,
23 schema::ServerDb,
24};
25
26impl<S, A, G, D> ServerState<S, A, G, D>
27where
28 S: StripeClient,
29 A: AppStoreClient,
30 G: GooglePlayClient,
31 D: DocumentService,
32{
33 pub async fn upsert_debug_info(
34 &self, context: RequestContext<UpsertDebugInfoRequest>,
35 ) -> Result<(), ServerError<UpsertDebugInfoError>> {
36 let mut lock = self.index_db.lock().await;
37
38 let db = lock.deref_mut();
39
40 let tx = db.begin_transaction()?;
41
42 if !Self::is_beta_user::<UpsertDebugInfoError>(db, &context.public_key) {
43 return Err(ClientError(UpsertDebugInfoError::NotPermissioned));
44 }
45
46 let owner = Owner(context.public_key);
47
48 let debug_info = context.request.debug_info.clone();
49 let new_panics_count = debug_info.panics.len();
50
51 let maybe_old_debug_info = db.debug_info.insert(
52 owner,
53 context.request.debug_info.lb_id,
54 context.request.debug_info,
55 )?;
56
57 let old_panics_count =
58 if let Some(debug_info) = maybe_old_debug_info { debug_info.panics.len() } else { 0 };
59
60 if new_panics_count > old_panics_count {
61 if let Some(panic) = debug_info.panics.first() {
62 warn!(?debug_info, "beta user experienced a panic");
63
64 self.send_panic_to_discord(&debug_info, panic).await?;
65 }
66 }
67
68 tx.drop_safely()?;
69
70 Ok(())
71 }
72
73 async fn send_panic_to_discord(
74 &self, debug_info: &lb_rs::service::debug::DebugInfo, panic: &str,
75 ) -> Result<(), ServerError<UpsertDebugInfoError>> {
76 let discord_webhook_url = match &self.config.server.discord_webhook_url {
77 Some(url) => url,
78 None => return Ok(()),
79 };
80
81 let maybe_new_line_index = panic.find('\n');
82 let mut panic_title = None;
83
84 if let Some(new_line_index) = maybe_new_line_index {
85 panic_title = Some(panic[0..new_line_index].to_string());
86 }
87
88 let payload = json!({
89 "username": "Panic Reporter",
90 "embeds": [{
91 "color": 14622784,
92 "author": { "name": debug_info.name },
93 "title": panic_title.unwrap_or("".to_string()),
94 }]
95 });
96
97 let debug_info_part = multipart::Part::bytes(serde_json::to_vec_pretty(&debug_info)?)
98 .file_name(format!("{}_{}_debug_info.json", debug_info.name, debug_info.panics.len()))
99 .mime_str("application/json")?;
100
101 let form = multipart::Form::new()
102 .part("file", debug_info_part)
103 .text("payload_json", payload.to_string());
104
105 let response = self
106 .discord_client
107 .post(discord_webhook_url)
108 .multipart(form)
109 .send()
110 .await?;
111
112 if response.status().is_success() {
113 info!("Notifed discord of a panic!");
114 } else {
115 warn!("Failed to notify discord: {:?} {:?}", response.status(), response.text().await?);
116 }
117 Ok(())
118 }
119
120 pub fn is_beta_user<E: Debug>(db: &ServerDb, public_key: &PublicKey) -> bool {
121 let is_beta = match db.accounts.get().get(&Owner(*public_key)) {
122 None => false,
123 Some(account) => BETA_USERS.contains(&account.username.as_str()),
124 };
125
126 is_beta
127 }
128}