mirror of
https://github.com/storytold/artcraft.git
synced 2026-11-04 17:38:10 +00:00
reshape app state endpoint
This commit is contained in:
1 parent
87f70d507f
commit
89ff405318
5 files changed
+211
-57
No files matched your search
+107
@@ -0,0 +1,107 @@
|
||||
use crate::http_server::session::lookup::user_session_extended::UserSessionExtended;
|
||||
use enums::by_table::users::user_feature_flag::UserFeatureFlag;
|
||||
use std::collections::BTreeSet;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
#[derive(Serialize, ToSchema, Clone)]
|
||||
pub struct AppStatePermissions {
|
||||
/// Moderation flag.
|
||||
/// Use this to determine if an account can act on other accounts.
|
||||
pub is_moderator: bool,
|
||||
|
||||
/// Collection of feature / rollout flags
|
||||
/// This is the proper place to detect if a user has access to some rollout (non-paywall) feature.
|
||||
/// NB: The BTreeSet maintains order so React doesn't introduce re-render state bugs when order changes
|
||||
pub feature_flags: BTreeSet<UserFeatureFlag>,
|
||||
|
||||
/// Legacy feature flags. Do not use.
|
||||
#[deprecated(note = "DO NOT USE. This is only to port old logic.")]
|
||||
pub legacy_permission_flags: AppStateLegacyPermissionFlags,
|
||||
}
|
||||
|
||||
#[derive(Serialize, ToSchema, Clone)]
|
||||
pub struct AppStateLegacyPermissionFlags {
|
||||
// Usage permissions:
|
||||
pub can_use_tts: bool,
|
||||
pub can_use_w2l: bool,
|
||||
pub can_delete_own_tts_results: bool,
|
||||
pub can_delete_own_w2l_results: bool,
|
||||
pub can_delete_own_account: bool,
|
||||
|
||||
// Contribution permissions:
|
||||
pub can_upload_tts_models: bool,
|
||||
pub can_upload_w2l_templates: bool,
|
||||
pub can_delete_own_tts_models: bool,
|
||||
pub can_delete_own_w2l_templates: bool,
|
||||
|
||||
// Moderation permissions:
|
||||
pub can_approve_w2l_templates: bool,
|
||||
pub can_edit_other_users_profiles: bool,
|
||||
pub can_edit_other_users_tts_models: bool,
|
||||
pub can_edit_other_users_w2l_templates: bool,
|
||||
pub can_delete_other_users_tts_models: bool,
|
||||
pub can_delete_other_users_tts_results: bool,
|
||||
pub can_delete_other_users_w2l_templates: bool,
|
||||
pub can_delete_other_users_w2l_results: bool,
|
||||
pub can_ban_users: bool,
|
||||
pub can_delete_users: bool,
|
||||
}
|
||||
|
||||
pub fn get_permissions(
|
||||
maybe_user: Option<&UserSessionExtended>
|
||||
) -> AppStatePermissions {
|
||||
match maybe_user {
|
||||
None => LOGGED_OUT_PERMISSIONS.clone(),
|
||||
Some(user) => AppStatePermissions {
|
||||
is_moderator: user.role.user_role_slug == "moderator",
|
||||
feature_flags: user.feature_flags.clone_flags(),
|
||||
legacy_permission_flags: AppStateLegacyPermissionFlags {
|
||||
can_use_tts: user.role.can_use_tts,
|
||||
can_use_w2l: user.role.can_use_w2l,
|
||||
can_delete_own_tts_results: user.role.can_delete_own_tts_results,
|
||||
can_delete_own_w2l_results: user.role.can_delete_own_w2l_results,
|
||||
can_delete_own_account: user.role.can_delete_own_account,
|
||||
can_upload_tts_models: user.role.can_upload_tts_models,
|
||||
can_upload_w2l_templates: user.role.can_upload_w2l_templates,
|
||||
can_delete_own_tts_models: user.role.can_delete_own_tts_models,
|
||||
can_delete_own_w2l_templates: user.role.can_delete_own_w2l_templates,
|
||||
can_approve_w2l_templates: user.role.can_approve_w2l_templates,
|
||||
can_edit_other_users_profiles: user.role.can_edit_other_users_profiles,
|
||||
can_edit_other_users_tts_models: user.role.can_edit_other_users_tts_models,
|
||||
can_edit_other_users_w2l_templates: user.role.can_edit_other_users_w2l_templates,
|
||||
can_delete_other_users_tts_models: user.role.can_delete_other_users_tts_models,
|
||||
can_delete_other_users_tts_results: user.role.can_delete_other_users_tts_results,
|
||||
can_delete_other_users_w2l_templates: user.role.can_delete_other_users_w2l_templates,
|
||||
can_delete_other_users_w2l_results: user.role.can_delete_other_users_w2l_results,
|
||||
can_ban_users: user.role.can_ban_users,
|
||||
can_delete_users: user.role.can_delete_users,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const LOGGED_OUT_PERMISSIONS : AppStatePermissions = AppStatePermissions {
|
||||
is_moderator: false,
|
||||
feature_flags: BTreeSet::new(),
|
||||
legacy_permission_flags: AppStateLegacyPermissionFlags {
|
||||
can_use_tts: false,
|
||||
can_use_w2l: false,
|
||||
can_delete_own_tts_results: false,
|
||||
can_delete_own_w2l_results: false,
|
||||
can_delete_own_account: false,
|
||||
can_upload_tts_models: false,
|
||||
can_upload_w2l_templates: false,
|
||||
can_delete_own_tts_models: false,
|
||||
can_delete_own_w2l_templates: false,
|
||||
can_approve_w2l_templates: false,
|
||||
can_edit_other_users_profiles: false,
|
||||
can_edit_other_users_tts_models: false,
|
||||
can_edit_other_users_w2l_templates: false,
|
||||
can_delete_other_users_tts_models: false,
|
||||
can_delete_other_users_tts_results: false,
|
||||
can_delete_other_users_w2l_templates: false,
|
||||
can_delete_other_users_w2l_results: false,
|
||||
can_ban_users: false,
|
||||
can_delete_users: false,
|
||||
},
|
||||
};
|
||||
+16
-5
@@ -1,5 +1,7 @@
|
||||
use billing_component::stripe::traits::internal_user_lookup::UserMetadata;
|
||||
use utoipa::ToSchema;
|
||||
use mysql_queries::queries::users::user_sessions::get_user_session_by_token::SessionUserRecord;
|
||||
use crate::http_server::session::lookup::user_session_extended::UserSessionExtended;
|
||||
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct AppStatePremiumInfo {
|
||||
@@ -7,6 +9,10 @@ pub struct AppStatePremiumInfo {
|
||||
/// having *either* a loyalty perk or a paid subscription.
|
||||
pub user_has_premium: bool,
|
||||
|
||||
/// If the user has free premium perks.
|
||||
/// This is from free loyalty program perks.
|
||||
pub user_has_free_premium: bool,
|
||||
|
||||
/// If the user has paid for a premium subscription, this will
|
||||
/// be true. Loyalty perks are not considered paid.
|
||||
pub user_has_paid_premium: bool,
|
||||
@@ -29,28 +35,33 @@ pub struct AppStateSubscriptionProductKey {
|
||||
}
|
||||
|
||||
pub fn get_premium_info(
|
||||
user_metadata: &UserMetadata,
|
||||
user_metadata: &UserSessionExtended,
|
||||
) -> AppStatePremiumInfo {
|
||||
|
||||
let maybe_loyalty_program = user_metadata
|
||||
.premium
|
||||
.maybe_loyalty_program_key
|
||||
.as_deref()
|
||||
.map(|lp| lp.to_string());
|
||||
|
||||
let active_subscriptions = user_metadata
|
||||
.existing_subscription_keys
|
||||
.premium
|
||||
.subscription_plans
|
||||
.iter()
|
||||
.map(|sub| AppStateSubscriptionProductKey {
|
||||
namespace: sub.internal_subscription_namespace.to_string(),
|
||||
product_slug: sub.internal_subscription_product_slug.to_string(),
|
||||
// TODO: Is this correct? Should it be externally facing?
|
||||
namespace: sub.subscription_namespace.to_string(),
|
||||
product_slug: sub.subscription_product_slug.to_string(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let user_has_paid_premium = !active_subscriptions.is_empty();
|
||||
let user_has_premium = user_has_paid_premium || maybe_loyalty_program.is_some();
|
||||
let user_has_free_premium = maybe_loyalty_program.is_some();
|
||||
let user_has_premium = user_has_paid_premium || user_has_free_premium;
|
||||
|
||||
AppStatePremiumInfo {
|
||||
user_has_premium,
|
||||
user_has_free_premium,
|
||||
user_has_paid_premium,
|
||||
maybe_loyalty_program,
|
||||
active_subscriptions,
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
use crate::http_server::common_responses::user_details_lite::UserDefaultAvatarInfo;
|
||||
use crate::http_server::session::lookup::user_session_extended::UserSessionExtended;
|
||||
use tokens::tokens::users::UserToken;
|
||||
use utoipa::ToSchema;
|
||||
|
||||
/// Information on the logged-in user's account.
|
||||
/// (This is a copy of the `UserDetailsLight` struct.)
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct AppStateUserInfo {
|
||||
/// The token for the user
|
||||
pub user_token: UserToken,
|
||||
|
||||
/// The unique username someone logs in with
|
||||
/// As of 2023-08-23, this is always lowercase
|
||||
pub username: String,
|
||||
|
||||
/// As of 2023-08-23, this is the username with capitalization
|
||||
/// (In the future, a display name can be customized by the user.)
|
||||
pub display_name: String,
|
||||
|
||||
/// Email hash for Gravatar
|
||||
/// Always set for now since login is email/username+password.
|
||||
/// In the future this will need to become an optional *OR* be filled with a bogus hash.
|
||||
pub gravatar_hash: String,
|
||||
|
||||
/// For users without a gravatar, we show one of our own avatars.
|
||||
pub default_avatar: UserDefaultAvatarInfo,
|
||||
|
||||
// In the future, we'll also support user-uploaded avatars that we store on our servers.
|
||||
}
|
||||
|
||||
pub fn get_user_info(
|
||||
user: &UserSessionExtended
|
||||
) -> AppStateUserInfo {
|
||||
AppStateUserInfo {
|
||||
default_avatar: UserDefaultAvatarInfo::from_username(&user.user.username),
|
||||
user_token: user.user_token_typed.clone(),
|
||||
username: user.user.username.to_string(),
|
||||
display_name: user.user.display_name.to_string(),
|
||||
gravatar_hash: user.user.email_gravatar_hash.to_string(),
|
||||
}
|
||||
}
|
||||
+2
@@ -1,4 +1,6 @@
|
||||
pub mod get_permissions;
|
||||
pub mod get_premium_info;
|
||||
pub mod get_server_info;
|
||||
pub mod get_status_alert;
|
||||
pub mod get_user_info;
|
||||
pub mod get_user_locale;
|
||||
+44
-52
@@ -1,6 +1,8 @@
|
||||
use crate::http_server::endpoints::app_state::components::get_permissions::{get_permissions, AppStatePermissions};
|
||||
use crate::http_server::endpoints::app_state::components::get_premium_info::{get_premium_info, AppStatePremiumInfo};
|
||||
use crate::http_server::endpoints::app_state::components::get_server_info::{get_server_info, AppStateServerInfo};
|
||||
use crate::http_server::endpoints::app_state::components::get_status_alert::{get_status_alert, AppStateStatusAlertInfo};
|
||||
use crate::http_server::endpoints::app_state::components::get_user_info::{get_user_info, AppStateUserInfo};
|
||||
use crate::http_server::endpoints::app_state::components::get_user_locale::{get_user_locale, AppStateUserLocale};
|
||||
use crate::state::server_state::ServerState;
|
||||
use actix_web::http::StatusCode;
|
||||
@@ -10,6 +12,7 @@ use billing_component::stripe::traits::internal_user_lookup::InternalUserLookup;
|
||||
use billing_component::users::http_endpoints::list_active_user_subscriptions_handler::ListActiveUserSubscriptionsError;
|
||||
use enums::by_table::user_ratings::entity_type::UserRatingEntityType;
|
||||
use enums::by_table::user_ratings::rating_value::UserRatingValue;
|
||||
use hostname::get;
|
||||
use http_server_common::response::serialize_as_json_error::serialize_as_json_error;
|
||||
use log::{error, info};
|
||||
use mysql_queries::composite_keys::by_table::user_ratings::user_rating_entity::UserRatingEntity;
|
||||
@@ -32,11 +35,12 @@ const REFRESH_INTERVAL: Duration = Duration::from_secs(60);
|
||||
// - server_info (once)
|
||||
// - detect_locale (once)
|
||||
// - status_alert_check (60 seconds)
|
||||
// TODO:
|
||||
// - active_subscriptions
|
||||
// TODO:
|
||||
// - session
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct AppStateResponse {
|
||||
/// All endpoints return `success = true` on 200.
|
||||
pub success: bool,
|
||||
|
||||
/// Tell the frontend client how fast to refresh their view of this state.
|
||||
@@ -57,35 +61,43 @@ pub struct AppStateResponse {
|
||||
pub maybe_alert: Option<AppStateStatusAlertInfo>,
|
||||
|
||||
/// Information on user locale (language codes, etc.)
|
||||
pub user_locale: AppStateUserLocale,
|
||||
pub locale: AppStateUserLocale,
|
||||
|
||||
/// Whether the user is logged in.
|
||||
pub logged_in: bool,
|
||||
|
||||
/// If the user is logged into an account with a valid session, this will
|
||||
/// contain the user's account info.
|
||||
pub maybe_user: Option<AppStateUserAccountInfo>,
|
||||
pub maybe_user_info: Option<AppStateUserInfo>,
|
||||
|
||||
/// Information on user permissions.
|
||||
pub permissions: AppStatePermissions,
|
||||
|
||||
/// Contains details oof the user's premium subscription status.
|
||||
pub maybe_premium: Option<AppStatePremiumInfo>,
|
||||
}
|
||||
|
||||
/// User account information.
|
||||
/// This is only for valid logged-in users.
|
||||
#[derive(Serialize, ToSchema)]
|
||||
pub struct AppStateUserAccountInfo {
|
||||
/// Details on the user's premium account status.
|
||||
pub premium: AppStatePremiumInfo,
|
||||
}
|
||||
// /// User account information.
|
||||
// /// This is only for valid logged-in users.
|
||||
// #[derive(Serialize, ToSchema)]
|
||||
// pub struct AppStateUserAccountInfo {
|
||||
// /// Information on the user's account
|
||||
// pub account: String,
|
||||
//
|
||||
// /// Details on the user's premium subscription status.
|
||||
// pub premium: AppStatePremiumInfo,
|
||||
// }
|
||||
|
||||
// =============== Error Response ===============
|
||||
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub enum AppStateError {
|
||||
BadInput(String),
|
||||
NotAuthorized,
|
||||
ServerError,
|
||||
}
|
||||
|
||||
impl ResponseError for AppStateError {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match *self {
|
||||
AppStateError::BadInput(_) => StatusCode::BAD_REQUEST,
|
||||
AppStateError::NotAuthorized => StatusCode::UNAUTHORIZED,
|
||||
AppStateError::ServerError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
@@ -113,33 +125,25 @@ impl std::fmt::Display for AppStateError {
|
||||
/// This single endpoint can replace the following endpoints:
|
||||
/// - `GET /detect_locale`
|
||||
/// - `GET /server_info`
|
||||
/// - `GET /v1/billing/active_subscriptions` (TODO: port new logic to old endpoint)
|
||||
/// - `GET /v1/status_alert_check`
|
||||
///
|
||||
/// TODO: - `GET /v1/billing/active_subscriptions`
|
||||
/// - `GET /v1/session`
|
||||
///
|
||||
/// This endpoint will probably grow new functionality in the future as well.
|
||||
#[utoipa::path(
|
||||
get,
|
||||
tag = "App State",
|
||||
path = "/v1/app_state",
|
||||
params(
|
||||
("entity_type", description = "The type of the entity being rated."),
|
||||
("entity_token", description = "Entity token"),
|
||||
),
|
||||
responses(
|
||||
(status = 200, description = "List User Bookmarks", body = AppStateResponse),
|
||||
(status = 400, description = "Bad input", body = AppStateError),
|
||||
(status = 401, description = "Not authorized", body = AppStateError),
|
||||
(status = 200, description = "Success response", body = AppStateResponse),
|
||||
(status = 500, description = "Server error", body = AppStateError),
|
||||
),
|
||||
)]
|
||||
pub async fn get_app_state_handler(
|
||||
http_request: HttpRequest,
|
||||
internal_user_lookup: web::Data<dyn InternalUserLookup>,
|
||||
server_state: web::Data<Arc<ServerState>>
|
||||
) -> Result<Json<AppStateResponse>, AppStateError>
|
||||
{
|
||||
/*
|
||||
let mut mysql_connection = server_state.mysql_pool.acquire()
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -149,48 +153,36 @@ pub async fn get_app_state_handler(
|
||||
|
||||
let maybe_user_session = server_state
|
||||
.session_checker
|
||||
.maybe_get_user_session_from_connection(&http_request, &mut mysql_connection)
|
||||
.maybe_get_user_session_extended_from_connection(&http_request, &mut mysql_connection)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
error!("Session checker error: {:?}", e);
|
||||
AppStateError::ServerError
|
||||
})?;
|
||||
|
||||
let user_session = match maybe_user_session {
|
||||
Some(session) => session,
|
||||
None => {
|
||||
info!("not logged in");
|
||||
return Err(AppStateError::NotAuthorized);
|
||||
}
|
||||
};
|
||||
*/
|
||||
|
||||
let maybe_user_metadata = internal_user_lookup
|
||||
.lookup_user_from_http_request(&http_request)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Error looking up user: {:?}", err);
|
||||
AppStateError::ServerError // NB: This was probably *our* fault.
|
||||
})?;
|
||||
|
||||
let server_info = get_server_info(&server_state);
|
||||
let user_locale = get_user_locale(&http_request);
|
||||
let maybe_alert = get_status_alert(&server_state);
|
||||
let locale = get_user_locale(&http_request);
|
||||
|
||||
let mut maybe_user = None;
|
||||
let mut maybe_user_info = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|session| get_user_info(session));
|
||||
|
||||
if let Some(user_metadata) = maybe_user_metadata {
|
||||
maybe_user = Some(AppStateUserAccountInfo {
|
||||
premium: get_premium_info(&user_metadata),
|
||||
});
|
||||
}
|
||||
let permissions = get_permissions(maybe_user_session.as_ref());
|
||||
|
||||
let maybe_premium = maybe_user_session
|
||||
.as_ref()
|
||||
.map(|session| get_premium_info(session));
|
||||
|
||||
Ok(Json(AppStateResponse {
|
||||
success: true,
|
||||
refresh_interval_millis: REFRESH_INTERVAL.as_millis(),
|
||||
server_info,
|
||||
user_locale,
|
||||
locale,
|
||||
maybe_alert,
|
||||
maybe_user,
|
||||
maybe_user_info,
|
||||
permissions,
|
||||
maybe_premium,
|
||||
logged_in: false,
|
||||
}))
|
||||
}
|
||||
Reference in new issue
Block a user