mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-06 10:11:31 +00:00
new session recording endpoint
This commit is contained in:
1 parent
d5f42e39e5
commit
2e8cf5a876
11 files changed
+273
-4
No files matched your search
+12
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "MySQL",
|
||||
"query": "\nINSERT INTO browser_session_logs\nSET\n token = ?,\n ip_address = ?,\n maybe_user_token = ?,\n maybe_anonymous_visitor_token = ?,\n maybe_last_action = ?\nON DUPLICATE KEY UPDATE\n maybe_last_action = ?,\n update_count = update_count + 1,\n maybe_last_updated_at = NOW()\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 6
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b1ee86b547130219e2a5958ff3eed295e87c46ffd5268b83a258c74f4ffc2e18"
|
||||
}
|
||||
@@ -26,6 +26,7 @@ pub(crate) enum TokenPrefix {
|
||||
AuditLog,
|
||||
BatchGeneration,
|
||||
BetaKey,
|
||||
BrowserSessionLog,
|
||||
Comment,
|
||||
DownloadJob,
|
||||
EmailSenderJob,
|
||||
@@ -96,6 +97,7 @@ impl PrefixGenerator for TokenPrefix {
|
||||
Self::AuditLog => "audit_",
|
||||
Self::BatchGeneration => "batch_g_",
|
||||
Self::BetaKey => "beta_key_",
|
||||
Self::BrowserSessionLog => "bsl_",
|
||||
Self::Comment => "comment_",
|
||||
Self::DownloadJob => "jdown_", // NB: Previously "JGUP:"
|
||||
Self::EmailSenderJob => "email_job_",
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
use std::fmt::Debug;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crate::prefixes::TokenPrefix;
|
||||
|
||||
/// The primary key for Audit Logs.
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, sqlx::Type, Debug, Serialize, Deserialize, ToSchema)]
|
||||
#[sqlx(transparent)]
|
||||
pub struct BrowserSessionLogToken(pub String);
|
||||
|
||||
impl_crockford_generator!(BrowserSessionLogToken, 32usize, TokenPrefix::BrowserSessionLog, CrockfordLower);
|
||||
impl_mysql_token_from_row!(BrowserSessionLogToken);
|
||||
impl_string_token!(BrowserSessionLogToken);
|
||||
@@ -4,6 +4,7 @@ pub mod api_tokens_internal;
|
||||
pub mod audit_logs;
|
||||
pub mod batch_generations;
|
||||
pub mod beta_keys;
|
||||
pub mod browser_session_logs;
|
||||
pub mod comments;
|
||||
pub mod email_sender_job_token;
|
||||
pub mod firehose_entries;
|
||||
@@ -16,6 +17,7 @@ pub mod model_weights;
|
||||
pub mod password_reset;
|
||||
pub mod prompts;
|
||||
pub mod sqlite;
|
||||
pub mod tags;
|
||||
pub mod tts_inference_jobs;
|
||||
pub mod tts_model_upload_jobs;
|
||||
pub mod tts_models;
|
||||
@@ -38,4 +40,3 @@ pub mod w2l_templates;
|
||||
pub mod zs_voice_dataset_samples;
|
||||
pub mod zs_voice_datasets;
|
||||
pub mod zs_voices;
|
||||
mod tags;
|
||||
@@ -0,0 +1 @@
|
||||
pub mod upsert_browser_session_log;
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
use anyhow::anyhow;
|
||||
use log::error;
|
||||
use sqlx::{MySqlPool, QueryBuilder};
|
||||
|
||||
use errors::AnyhowResult;
|
||||
use tokens::tokens::anonymous_visitor_tracking::AnonymousVisitorTrackingToken;
|
||||
use tokens::tokens::browser_session_logs::BrowserSessionLogToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
|
||||
pub struct UpsertBrowserSessionLogArgs<'a> {
|
||||
pub maybe_log_token: Option<&'a BrowserSessionLogToken>,
|
||||
|
||||
pub ip_address: &'a str,
|
||||
pub maybe_user_token: Option<&'a UserToken>,
|
||||
pub maybe_visitor_token: Option<&'a AnonymousVisitorTrackingToken>,
|
||||
|
||||
pub maybe_last_action: Option<&'a str>,
|
||||
|
||||
pub mysql_pool: &'a MySqlPool,
|
||||
}
|
||||
|
||||
pub async fn upsert_browser_session_log<'a>(args: UpsertBrowserSessionLogArgs<'a>) -> AnyhowResult<BrowserSessionLogToken> {
|
||||
|
||||
let token = args.maybe_log_token
|
||||
.map(|token| token.clone())
|
||||
.unwrap_or_else(|| BrowserSessionLogToken::generate());
|
||||
|
||||
let maybe_last_action = args.maybe_last_action
|
||||
.map(|action| {
|
||||
let mut action = action.trim().to_string();
|
||||
action.truncate(32); // NB: Field is 32 wide.
|
||||
action
|
||||
});
|
||||
|
||||
let query_result = sqlx::query!(
|
||||
r#"
|
||||
INSERT INTO browser_session_logs
|
||||
SET
|
||||
token = ?,
|
||||
ip_address = ?,
|
||||
maybe_user_token = ?,
|
||||
maybe_anonymous_visitor_token = ?,
|
||||
maybe_last_action = ?
|
||||
ON DUPLICATE KEY UPDATE
|
||||
maybe_last_action = ?,
|
||||
update_count = update_count + 1,
|
||||
maybe_last_updated_at = NOW()
|
||||
"#,
|
||||
token.as_str(),
|
||||
args.ip_address,
|
||||
args.maybe_user_token.map(|t| t.as_str()),
|
||||
args.maybe_visitor_token.map(|t| t.as_str()),
|
||||
maybe_last_action.as_deref(),
|
||||
maybe_last_action.as_deref(),
|
||||
)
|
||||
.execute(args.mysql_pool)
|
||||
.await;
|
||||
|
||||
match query_result {
|
||||
Ok(_) => Ok(token),
|
||||
Err(err) => {
|
||||
error!("Error with query: {:?}", &err);
|
||||
Err(anyhow!("query error: {:?}", &err))
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ pub mod audit_logs;
|
||||
pub mod batch_generations;
|
||||
pub mod beta_keys;
|
||||
pub mod billing;
|
||||
pub mod browser_session_logs;
|
||||
pub mod comments;
|
||||
pub mod email_sender_jobs;
|
||||
pub mod entity_stats;
|
||||
|
||||
@@ -26,6 +26,7 @@ use enums::common::visibility::Visibility;
|
||||
use enums::no_table::style_transfer::style_transfer_name::StyleTransferName;
|
||||
use tokens::tokens::batch_generations::*;
|
||||
use tokens::tokens::beta_keys::*;
|
||||
use tokens::tokens::browser_session_logs::*;
|
||||
use tokens::tokens::comments::*;
|
||||
use tokens::tokens::generic_inference_jobs::*;
|
||||
use tokens::tokens::media_files::*;
|
||||
@@ -48,6 +49,7 @@ use crate::http_server::common_responses::pagination_cursors::PaginationCursors;
|
||||
use crate::http_server::common_responses::pagination_page::PaginationPage;
|
||||
use crate::http_server::common_responses::simple_entity_stats::SimpleEntityStats;
|
||||
use crate::http_server::common_responses::weights_cover_image_details::*;
|
||||
use crate::http_server::endpoints::analytics::log_browser_session_handler::*;
|
||||
use crate::http_server::endpoints::beta_keys::create_beta_keys_handler::*;
|
||||
use crate::http_server::endpoints::beta_keys::list_beta_keys_handler::*;
|
||||
use crate::http_server::endpoints::beta_keys::redeem_beta_key_handler::*;
|
||||
@@ -118,6 +120,7 @@ use crate::http_server::web_utils::response_success_helpers::*;
|
||||
#[openapi(
|
||||
paths(
|
||||
billing_component::stripe::http_endpoints::checkout::create::stripe_create_checkout_session_json_handler::stripe_create_checkout_session_json_handler,
|
||||
crate::http_server::endpoints::analytics::log_browser_session_handler::log_browser_session_handler,
|
||||
crate::http_server::endpoints::beta_keys::create_beta_keys_handler::create_beta_keys_handler,
|
||||
crate::http_server::endpoints::beta_keys::list_beta_keys_handler::list_beta_keys_handler,
|
||||
crate::http_server::endpoints::beta_keys::redeem_beta_key_handler::redeem_beta_key_handler,
|
||||
@@ -189,6 +192,7 @@ use crate::http_server::web_utils::response_success_helpers::*;
|
||||
// Tokens
|
||||
BatchGenerationToken,
|
||||
BetaKeyToken,
|
||||
BrowserSessionLogToken,
|
||||
CommentToken,
|
||||
InferenceJobToken,
|
||||
MediaFileToken,
|
||||
@@ -334,11 +338,11 @@ use crate::http_server::web_utils::response_success_helpers::*;
|
||||
GetWeightError,
|
||||
GetWeightPathInfo,
|
||||
GetWeightResponse,
|
||||
InferenceJobStatusResponsePayload,
|
||||
InferenceJobTokenType,
|
||||
InferTtsError,
|
||||
InferTtsRequest,
|
||||
InferTtsSuccessResponse,
|
||||
InferenceJobStatusResponsePayload,
|
||||
InferenceJobTokenType,
|
||||
ListAvailableWeightsQuery,
|
||||
ListAvailableWeightsSuccessResponse,
|
||||
ListBetaKeysError,
|
||||
@@ -387,6 +391,9 @@ use crate::http_server::web_utils::response_success_helpers::*;
|
||||
ListWeightsByUserError,
|
||||
ListWeightsByUserPathInfo,
|
||||
ListWeightsByUserSuccessResponse,
|
||||
LogBrowserSessionError,
|
||||
LogBrowserSessionRequest,
|
||||
LogBrowserSessionSuccessResponse,
|
||||
LoginErrorResponse,
|
||||
LoginErrorType,
|
||||
LoginRequest,
|
||||
@@ -397,8 +404,8 @@ use crate::http_server::web_utils::response_success_helpers::*;
|
||||
MediaFileForUserListItem,
|
||||
MediaFileInfo,
|
||||
MediaFileListItem,
|
||||
MediaFilesByBatchListItem,
|
||||
MediaFileUploadError,
|
||||
MediaFilesByBatchListItem,
|
||||
ModelWeightForList,
|
||||
ModelWeightSearchResult,
|
||||
PinnedMediaFile,
|
||||
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use actix_web::{HttpRequest, HttpResponse, web};
|
||||
use actix_web::error::ResponseError;
|
||||
use actix_web::http::StatusCode;
|
||||
use log::warn;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
use crockford::crockford_entropy_lower;
|
||||
use enums::by_table::beta_keys::beta_key_product::BetaKeyProduct;
|
||||
use enums::by_table::comments::comment_entity_type::CommentEntityType;
|
||||
use http_server_common::request::get_request_ip::get_request_ip;
|
||||
use mysql_queries::queries::beta_keys::insert_batch_beta_keys::{insert_batch_beta_keys, InsertBatchArgs};
|
||||
use mysql_queries::queries::browser_session_logs::upsert_browser_session_log::{upsert_browser_session_log, UpsertBrowserSessionLogArgs};
|
||||
use mysql_queries::queries::comments::comment_entity_token::CommentEntityToken;
|
||||
use mysql_queries::queries::comments::insert_comment::{insert_comment, InsertCommentArgs};
|
||||
use mysql_queries::queries::users::user_profiles::get_user_profile_by_username::get_user_profile_by_username;
|
||||
use tokens::tokens::browser_session_logs::BrowserSessionLogToken;
|
||||
use tokens::tokens::comments::CommentToken;
|
||||
use tokens::tokens::media_files::MediaFileToken;
|
||||
use tokens::tokens::model_weights::ModelWeightToken;
|
||||
use tokens::tokens::tts_models::TtsModelToken;
|
||||
use tokens::tokens::tts_results::TtsResultToken;
|
||||
use tokens::tokens::users::UserToken;
|
||||
use tokens::tokens::w2l_results::W2lResultToken;
|
||||
use tokens::tokens::w2l_templates::W2lTemplateToken;
|
||||
use user_input_common::check_for_slurs::contains_slurs;
|
||||
use user_input_common::markdown_to_html::markdown_to_html;
|
||||
|
||||
use crate::http_server::endpoints::moderation::user_feature_flags::edit_user_feature_flags_handler::EditUserFeatureFlagsError;
|
||||
use crate::http_server::web_utils::require_moderator::{require_moderator, RequireModeratorError};
|
||||
use crate::http_server::web_utils::require_user_session::RequireUserSessionError;
|
||||
use crate::http_server::web_utils::response_error_helpers::to_simple_json_error;
|
||||
use crate::server_state::ServerState;
|
||||
|
||||
#[derive(Deserialize, ToSchema)]
|
||||
pub struct LogBrowserSessionRequest {
|
||||
/// Make the first request to this endpoint without a token.
|
||||
/// The server will give you a token to use for every subsequent call.
|
||||
/// Using this, we can calculate how sticky product usage is.
|
||||
maybe_log_token: Option<BrowserSessionLogToken>,
|
||||
|
||||
/// OPTIONAL: Optionally include an up to 32-character identifier of what
|
||||
/// action the user was last performing.
|
||||
maybe_last_action: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct LogBrowserSessionSuccessResponse {
|
||||
pub success: bool,
|
||||
|
||||
/// Store and use this token in subsequent requests.
|
||||
/// If the user closes the browser, do not save it in session storage.
|
||||
/// You can get a new token with each browser session.
|
||||
pub log_token: BrowserSessionLogToken,
|
||||
}
|
||||
|
||||
#[derive(Debug, ToSchema)]
|
||||
pub enum LogBrowserSessionError {
|
||||
BadInput(String),
|
||||
NotAuthorized,
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl ResponseError for LogBrowserSessionError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
LogBrowserSessionError::BadInput(_) => StatusCode::BAD_REQUEST,
|
||||
LogBrowserSessionError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
LogBrowserSessionError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let error_reason = match self {
|
||||
LogBrowserSessionError::BadInput(reason) => reason.to_string(),
|
||||
LogBrowserSessionError::NotAuthorized => "unauthorized".to_string(),
|
||||
LogBrowserSessionError::ServerError => "server error".to_string(),
|
||||
};
|
||||
|
||||
to_simple_json_error(&error_reason, self.status_code())
|
||||
}
|
||||
}
|
||||
|
||||
// NB: Not using derive_more::Display since Clion doesn't understand it.
|
||||
impl fmt::Display for LogBrowserSessionError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self)
|
||||
}
|
||||
}
|
||||
|
||||
/// Log a browser session (so we can calculate duration, etc.)
|
||||
#[utoipa::path(
|
||||
post,
|
||||
tag = "Analytics",
|
||||
path = "/v1/analytics/log_session",
|
||||
responses(
|
||||
(status = 200, description = "Success", body = LogBrowserSessionSuccessResponse),
|
||||
(status = 400, description = "Bad input", body = LogBrowserSessionError),
|
||||
(status = 401, description = "Not authorized", body = LogBrowserSessionError),
|
||||
(status = 500, description = "Server error", body = LogBrowserSessionError),
|
||||
),
|
||||
params(
|
||||
("request" = LogBrowserSessionRequest, description = "Payload for Request"),
|
||||
)
|
||||
)]
|
||||
pub async fn log_browser_session_handler(
|
||||
http_request: HttpRequest,
|
||||
request: web::Json<LogBrowserSessionRequest>,
|
||||
server_state: web::Data<Arc<ServerState>>,
|
||||
) -> Result<HttpResponse, LogBrowserSessionError>
|
||||
{
|
||||
let mut mysql_connection = server_state.mysql_pool
|
||||
.acquire()
|
||||
.await
|
||||
.map_err(|err| {
|
||||
warn!("MySql pool error: {:?}", err);
|
||||
LogBrowserSessionError::ServerError
|
||||
})?;
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
warn!("Session checker error: {:?}", e);
|
||||
LogBrowserSessionError::ServerError
|
||||
})?;
|
||||
|
||||
let maybe_avt_token = server_state
|
||||
.avt_cookie_manager
|
||||
.get_avt_token_from_request(&http_request);
|
||||
|
||||
let ip_address = get_request_ip(&http_request);
|
||||
|
||||
let token = upsert_browser_session_log(UpsertBrowserSessionLogArgs {
|
||||
maybe_log_token: request.maybe_log_token.as_ref(),
|
||||
ip_address: &ip_address,
|
||||
maybe_user_token: maybe_user_session.as_ref().map(|s| &s.user_token),
|
||||
maybe_visitor_token: maybe_avt_token.as_ref(),
|
||||
maybe_last_action: request.maybe_last_action.as_deref(),
|
||||
mysql_pool: &server_state.mysql_pool,
|
||||
}).await.map_err(|err| {
|
||||
warn!("Error inserting beta keys: {:?}", err);
|
||||
LogBrowserSessionError::ServerError
|
||||
})?;
|
||||
|
||||
let response = LogBrowserSessionSuccessResponse {
|
||||
success: true,
|
||||
log_token: token,
|
||||
};
|
||||
|
||||
let body = serde_json::to_string(&response)
|
||||
.map_err(|_e| LogBrowserSessionError::ServerError)?;
|
||||
|
||||
Ok(HttpResponse::Ok()
|
||||
.content_type("application/json")
|
||||
.body(body))
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
pub mod log_browser_session_handler;
|
||||
@@ -35,3 +35,4 @@ pub mod voice_designer;
|
||||
pub mod w2l;
|
||||
pub mod weights;
|
||||
pub mod workflows;
|
||||
pub mod analytics;
|
||||
Reference in new issue
Block a user