mirror of
https://github.com/storytold/artcraft.git
synced 2026-10-22 09:39:31 -04:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d497f2cb7 |
Generated
+12
@@ -679,6 +679,7 @@ dependencies = [
|
||||
"mimetypes",
|
||||
"once_cell",
|
||||
"openai_sora_client",
|
||||
"os_info",
|
||||
"primitives",
|
||||
"rand 0.9.1",
|
||||
"reqwest 0.12.22",
|
||||
@@ -7610,6 +7611,17 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_info"
|
||||
version = "3.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3"
|
||||
dependencies = [
|
||||
"log",
|
||||
"plist",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
|
||||
@@ -17,6 +17,14 @@ CREATE TABLE analytics_app_active_users (
|
||||
-- The "user agent" or app version string of the client.
|
||||
app_version VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The OS platform for the client.
|
||||
os_platform VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The OS platform version for the client.
|
||||
os_version VARCHAR(64) DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- How long the current user's session has been open
|
||||
session_duration_seconds INT(10) UNSIGNED DEFAULT NULL,
|
||||
@@ -41,6 +49,9 @@ CREATE TABLE analytics_app_active_users (
|
||||
-- ========== INDICES ==========
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (app_namespace, user_token), -- For now
|
||||
INDEX idx_os_platform (os_platform),
|
||||
INDEX idx_os_version (os_version),
|
||||
INDEX idx_app_version (app_version),
|
||||
INDEX idx_last_active_at (last_active_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
@@ -17,6 +17,14 @@ CREATE TABLE analytics_app_active_users (
|
||||
-- The "user agent" or app version string of the client.
|
||||
app_version VARCHAR(255) DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The OS platform for the client.
|
||||
os_platform VARCHAR(16) DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- The OS platform version for the client.
|
||||
os_version VARCHAR(64) DEFAULT NULL,
|
||||
|
||||
-- Updated on every write.
|
||||
-- How long the current user's session has been open
|
||||
session_duration_seconds INT(10) UNSIGNED DEFAULT NULL,
|
||||
@@ -41,6 +49,9 @@ CREATE TABLE analytics_app_active_users (
|
||||
-- ========== INDICES ==========
|
||||
PRIMARY KEY (id),
|
||||
UNIQUE KEY (app_namespace, user_token), -- For now
|
||||
INDEX idx_app_version (app_version),
|
||||
INDEX idx_os_platform (os_platform),
|
||||
INDEX idx_os_version (os_version),
|
||||
INDEX idx_last_active_at (last_active_at)
|
||||
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
||||
|
||||
@@ -5,11 +5,17 @@ pub const LOG_ACTIVE_USER_PATH: &str = "/v1/analytics/active_user";
|
||||
|
||||
#[derive(Serialize, Deserialize, ToSchema, Debug)]
|
||||
pub struct LogAppActiveUserRequest {
|
||||
/// An override for the application platform/OS (windows, mac, linux).
|
||||
pub maybe_os_platform: Option<String>,
|
||||
|
||||
/// An override for the version of the OS (e.g. 10.15.7, 11, 22.04).
|
||||
pub maybe_os_version: Option<String>,
|
||||
|
||||
/// An override for the application name.
|
||||
/// If set together with `maybe_app_version`, the two will be
|
||||
/// concatenated as `{maybe_app_name}/{maybe_app_version}`.
|
||||
pub maybe_app_name: Option<String>,
|
||||
|
||||
|
||||
/// An override for the application version.
|
||||
/// If set together with `maybe_app_name`, the two will be
|
||||
/// concatenated as `{maybe_app_name}/{maybe_app_version}`.
|
||||
|
||||
@@ -15,6 +15,14 @@ pub async fn log_active_user(
|
||||
|
||||
let mut query = HashMap::new();
|
||||
|
||||
if let Some(os_platform) = request.maybe_os_platform {
|
||||
query.insert("maybe_os_platform".to_string(), os_platform);
|
||||
}
|
||||
|
||||
if let Some(os_version) = request.maybe_os_version {
|
||||
query.insert("maybe_os_version".to_string(), os_version);
|
||||
}
|
||||
|
||||
if let Some(app_name) = request.maybe_app_name {
|
||||
query.insert("maybe_app_name".to_string(), app_name);
|
||||
}
|
||||
|
||||
@@ -83,13 +83,23 @@ fn get_route(api_host: &ApiHost, route_path: &str, query_params: &HashMap<String
|
||||
|
||||
// NB: This is not safe. It doesn't handle URL encoding, "?", "&", etc.
|
||||
for (key, value) in query_params {
|
||||
builder.add_param(key, value);
|
||||
let value = filter_string(value);
|
||||
builder.add_param(key, &value);
|
||||
}
|
||||
|
||||
// NB: This isn't very safe.
|
||||
builder.build()
|
||||
}
|
||||
|
||||
// TODO: Please stop using this URL Builder library. It's not very safe or intuitive.
|
||||
fn filter_string(string: &str) -> String {
|
||||
string.trim()
|
||||
.replace(" ", "")
|
||||
.replace("&", "")
|
||||
.replace("=", "")
|
||||
.replace("?", "")
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -57,15 +57,16 @@ image = "0.25.5"
|
||||
indexmap.workspace = true
|
||||
log.workspace = true
|
||||
once_cell.workspace = true
|
||||
os_info = { version = "3.12.0", default-features = false }
|
||||
rand.workspace = true
|
||||
reqwest = { workspace = true, features = ["multipart", "stream"], default-features = false } # NB(bt,2025-03-10): default-features = false on Windows while I debug openssl / rustls
|
||||
serde.workspace = true
|
||||
serde_derive.workspace = true
|
||||
serde_json.workspace = true
|
||||
tauri.workspace = true
|
||||
tauri-plugin-dialog.workspace = true
|
||||
tauri-plugin-log.workspace = true
|
||||
tauri-plugin-upload.workspace = true
|
||||
tauri.workspace = true
|
||||
tempdir.workspace = true
|
||||
tempfile.workspace = true
|
||||
tokio = { version = "1.42", features = ["rt", "rt-multi-thread", "fs"] }
|
||||
|
||||
@@ -17,6 +17,15 @@ impl OsPlatform {
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn maybe_get_str() -> Option<&'static str> {
|
||||
match Self::maybe_get() {
|
||||
Some(Self::Linux) => Some("linux"),
|
||||
Some(Self::Windows) => Some("windows"),
|
||||
Some(Self::MacOs) => Some("macos"), // NB: "apple" can become "macos".
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get() -> Self {
|
||||
Self::maybe_get().unwrap_or(Self::Linux)
|
||||
|
||||
+14
@@ -2,6 +2,7 @@ use crate::core::events::basic_sendable_event_trait::BasicSendableEvent;
|
||||
use crate::core::state::app_env_configs::app_env_configs::AppEnvConfigs;
|
||||
use crate::core::state::data_dir::app_data_root::AppDataRoot;
|
||||
use crate::core::state::data_dir::trait_data_subdir::DataSubdir;
|
||||
use crate::core::state::os_platform::OsPlatform;
|
||||
use crate::core::state::task_database::TaskDatabase;
|
||||
use crate::services::storyteller::state::storyteller_credential_manager::StorytellerCredentialManager;
|
||||
use anyhow::anyhow;
|
||||
@@ -17,6 +18,7 @@ use reqwest::Url;
|
||||
use sqlite_tasks::queries::list_tasks_by_provider_and_tokens::{list_tasks_by_provider_and_tokens, ListTasksArgs, Task};
|
||||
use sqlite_tasks::queries::update_task_status::{update_task_status, UpdateTaskArgs};
|
||||
use std::time::Instant;
|
||||
use os_info::Info;
|
||||
use storyteller_client::analytics::log_active_user::log_active_user;
|
||||
use storyteller_client::credentials::storyteller_credential_set::StorytellerCredentialSet;
|
||||
use storyteller_client::error::api_error::ApiError;
|
||||
@@ -36,11 +38,13 @@ pub async fn storyteller_activity_thread(
|
||||
storyteller_creds_manager: StorytellerCredentialManager,
|
||||
) -> ! {
|
||||
let startup = Instant::now();
|
||||
let os_info = os_info::get();
|
||||
loop {
|
||||
let res = polling_loop(
|
||||
&app_env_configs,
|
||||
&storyteller_creds_manager,
|
||||
startup,
|
||||
&os_info,
|
||||
).await;
|
||||
if let Err(err) = res {
|
||||
error!("An error occurred: {:?}", err);
|
||||
@@ -54,6 +58,7 @@ async fn polling_loop(
|
||||
app_env_configs: &AppEnvConfigs,
|
||||
storyteller_creds_manager: &StorytellerCredentialManager,
|
||||
startup: Instant,
|
||||
os_info: &Info,
|
||||
) -> AnyhowResult<()> {
|
||||
loop {
|
||||
let creds = storyteller_creds_manager.get_credentials()?;
|
||||
@@ -74,9 +79,18 @@ async fn polling_loop(
|
||||
|
||||
let time_since_startup = Instant::now().duration_since(startup);
|
||||
|
||||
let maybe_os_platform = OsPlatform::maybe_get_str()
|
||||
.map(|s| s.to_string());
|
||||
|
||||
let maybe_os_version = Some(os_info.version().to_string())
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty());
|
||||
|
||||
let request = LogAppActiveUserRequest {
|
||||
maybe_app_name: Some(CLIENT_NAME.to_string()),
|
||||
maybe_app_version: Some(CLIENT_VERSION.to_string()),
|
||||
maybe_os_platform,
|
||||
maybe_os_version,
|
||||
maybe_session_duration_seconds: Some(time_since_startup.as_secs()),
|
||||
};
|
||||
|
||||
|
||||
+10
@@ -13,6 +13,8 @@ pub struct UpsertAnalyticsAppActiveUser<'a> {
|
||||
pub user_token: &'a UserToken,
|
||||
pub ip_address: &'a str,
|
||||
pub app_version: Option<&'a str>,
|
||||
pub os_platform: Option<&'a str>,
|
||||
pub os_version: Option<&'a str>,
|
||||
pub session_duration_seconds: Option<u64>,
|
||||
}
|
||||
|
||||
@@ -25,6 +27,8 @@ SET
|
||||
app_namespace = ?,
|
||||
user_token = ?,
|
||||
app_version = ?,
|
||||
os_platform = ?,
|
||||
os_version = ?,
|
||||
session_duration_seconds = ?,
|
||||
ip_address = ?,
|
||||
measurement_count = measurement_count + 1,
|
||||
@@ -32,6 +36,8 @@ SET
|
||||
last_active_at = NOW()
|
||||
ON DUPLICATE KEY UPDATE
|
||||
app_version = ?,
|
||||
os_platform = ?,
|
||||
os_version = ?,
|
||||
session_duration_seconds = ?,
|
||||
ip_address = ?,
|
||||
measurement_count = measurement_count + 1,
|
||||
@@ -41,10 +47,14 @@ ON DUPLICATE KEY UPDATE
|
||||
self.namespace.to_str(),
|
||||
self.user_token.as_str(),
|
||||
self.app_version,
|
||||
self.os_platform,
|
||||
self.os_version,
|
||||
self.session_duration_seconds,
|
||||
self.ip_address,
|
||||
// Update case
|
||||
self.app_version,
|
||||
self.os_platform,
|
||||
self.os_version,
|
||||
self.session_duration_seconds,
|
||||
self.ip_address,
|
||||
)
|
||||
|
||||
+2
@@ -94,6 +94,8 @@ pub async fn log_app_active_user_handler(
|
||||
user_token: &user_token,
|
||||
ip_address: &ip_address,
|
||||
app_version: app_version.as_deref(),
|
||||
os_platform: request.maybe_os_platform.as_deref(),
|
||||
os_version: request.maybe_os_version.as_deref(),
|
||||
session_duration_seconds: request.maybe_session_duration_seconds,
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user