v0.2.12-pre.003
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-solprices-desk/src/app_state.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Shared backend state owned by the SOL Prices Desk Tauri application.
|
||||
|
||||
@@ -7,12 +7,13 @@
|
||||
pub(crate) struct AppState {
|
||||
config_management: ksp_config_lib::ConfigManagement,
|
||||
logging_runtime: std::sync::Mutex<LoggingRuntimeState>,
|
||||
offchain_transport_startup: crate::OffchainTransportStartup,
|
||||
splash_settings: crate::SplashSettings,
|
||||
splash_sequence_started: std::sync::atomic::AtomicBool,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
/// Initializes Config ownership, Logging and the common desktop splash state without creating the Off-chain runtime reserved for `pre.003`.
|
||||
impl crate::AppState {
|
||||
/// Initializes Config ownership, composite-managed Logging and the Config-selected Off-chain Transport runtime.
|
||||
pub(crate) fn initialize(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<Self> {
|
||||
let config_management = crate::config_management(arguments);
|
||||
let config_management = match config_management {
|
||||
@@ -29,6 +30,11 @@ impl AppState {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let offchain_transport_startup = crate::initialize_offchain_transport(&config_management);
|
||||
let offchain_transport_startup = match offchain_transport_startup {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let splash_settings = crate::SplashSettings::load();
|
||||
let splash_settings = match splash_settings {
|
||||
std::result::Result::Ok(value) => value,
|
||||
@@ -46,12 +52,13 @@ impl AppState {
|
||||
fallback_active: logging_startup.fallback_active,
|
||||
startup_diagnostic: logging_startup.startup_diagnostic,
|
||||
}),
|
||||
offchain_transport_startup,
|
||||
splash_settings,
|
||||
splash_sequence_started: std::sync::atomic::AtomicBool::new(false),
|
||||
});
|
||||
}
|
||||
|
||||
/// Builds the safe scaffold runtime status exposed before market-price composition is introduced.
|
||||
/// Builds the safe runtime status exposed before provider rows and refresh commands are introduced.
|
||||
pub(crate) fn runtime_status(&self) -> ksp_core_lib::Result<crate::RuntimeStatusDto> {
|
||||
let document_count = self.config_management.engine().registry().descriptors().count();
|
||||
let document_count = u32::try_from(document_count);
|
||||
@@ -61,7 +68,7 @@ impl AppState {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_APP_STATE_INVALID,
|
||||
"Config registry contains too many descriptors for the SOL Prices Desk scaffold DTO",
|
||||
"Config registry contains too many descriptors for the SOL Prices Desk runtime DTO",
|
||||
)
|
||||
.with_source(error),
|
||||
);
|
||||
@@ -78,12 +85,15 @@ impl AppState {
|
||||
},
|
||||
};
|
||||
let _keep_guard_alive = &runtime.guard;
|
||||
let offchain = self.offchain_transport_startup.resolved();
|
||||
return std::result::Result::Ok(crate::RuntimeStatusDto {
|
||||
application_version: env!("CARGO_PKG_VERSION").to_owned(),
|
||||
active_composite_profile: self.offchain_transport_startup.composite_profile_id().to_owned(),
|
||||
active_logging_profile: runtime.active_profile_id.clone(),
|
||||
active_offchain_profile: offchain.profile_id().to_owned(),
|
||||
application_version: env!("CARGO_PKG_VERSION").to_owned(),
|
||||
config_document_count: document_count,
|
||||
fallback_logging_active: runtime.fallback_active,
|
||||
shell_phase: "pre.002-scaffold".to_owned(),
|
||||
shell_phase: "pre.003-offchain-bootstrap".to_owned(),
|
||||
startup_diagnostic: runtime.startup_diagnostic.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// file: crates/ksp-app-solprices-desk/src/bootstrap.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Config and Logging bootstrap for the SOL Prices Desk scaffold.
|
||||
//! Config composite and Logging bootstrap for SOL Prices Desk.
|
||||
|
||||
/// Crate-internal Logging startup state shared by the application state.
|
||||
pub(crate) struct LoggingStartup {
|
||||
@@ -43,7 +43,51 @@ pub(crate) fn config_management(arguments: &[std::ffi::OsString]) -> ksp_core_li
|
||||
return std::result::Result::Ok(ksp_config_lib::ConfigManagement::new(engine));
|
||||
}
|
||||
|
||||
/// Initializes Logging from the standard Config document until the SOL Prices composite is introduced by `pre.003`.
|
||||
/// Loads the concrete SOL Prices Desk Config composite using its registered logical `file_id` and autonomous default profile.
|
||||
pub(crate) fn load_solprices_desk_composite(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<ksp_config_lib::ResolvedConfigComposite> {
|
||||
let file_id = ksp_config_lib::ConfigFileId::new(ksp_config_lib::FILE_ID_COMPOSITE_KSP_APP_SOLPRICES_DESK);
|
||||
let file_id = match file_id {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
return management.engine().load_resolved_composite(&file_id, std::option::Option::None);
|
||||
}
|
||||
|
||||
/// Returns one required standard profile from the SOL Prices Desk composite after validating the referenced logical `file_id`.
|
||||
pub(crate) fn required_composite_component_profile(
|
||||
composite: &ksp_config_lib::ResolvedConfigComposite,
|
||||
component_id: &str,
|
||||
expected_file_id: &str,
|
||||
) -> ksp_core_lib::Result<ksp_config_lib::ResolvedConfigProfile> {
|
||||
let component = composite.component(component_id);
|
||||
let component = match component {
|
||||
std::option::Option::Some(value) => value,
|
||||
std::option::Option::None => {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID, "SOL Prices Desk composite is missing a required component")
|
||||
.with_context("composite_file_id", composite.file_id().as_str())
|
||||
.with_context("composite_profile_id", composite.profile_id())
|
||||
.with_context("component_id", component_id),
|
||||
);
|
||||
},
|
||||
};
|
||||
if component.resolved().file_id().as_str() != expected_file_id {
|
||||
return std::result::Result::Err(
|
||||
ksp_core_lib::Error::new(
|
||||
crate::ERROR_CODE_CONFIG_COMPOSITE_INVALID,
|
||||
"SOL Prices Desk composite component references an unexpected Config document",
|
||||
)
|
||||
.with_context("composite_file_id", composite.file_id().as_str())
|
||||
.with_context("composite_profile_id", composite.profile_id())
|
||||
.with_context("component_id", component_id)
|
||||
.with_context("expected_file_id", expected_file_id)
|
||||
.with_context("actual_file_id", component.resolved().file_id().as_str()),
|
||||
);
|
||||
}
|
||||
return std::result::Result::Ok(component.resolved().clone());
|
||||
}
|
||||
|
||||
/// Initializes Logging from the SOL Prices Desk composite, with the same bounded fallback policy as the other KSP desks.
|
||||
pub(crate) fn initialize_logging(
|
||||
management: &ksp_config_lib::ConfigManagement,
|
||||
runtime_identity: &ksp_logging_lib::LoggingRuntimeIdentity,
|
||||
@@ -54,7 +98,7 @@ pub(crate) fn initialize_logging(
|
||||
let guard = ksp_logging_lib::initialize_with_identity(&settings, runtime_identity);
|
||||
match guard {
|
||||
std::result::Result::Ok(guard) => {
|
||||
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_BOOTSTRAP, active_profile = active_profile_id.as_str(), "initialized SOL Prices Desk scaffold logging from managed configuration");
|
||||
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_BOOTSTRAP, active_profile = active_profile_id.as_str(), "initialized SOL Prices Desk logging from composite-managed configuration");
|
||||
std::result::Result::Ok(LoggingStartup {
|
||||
guard,
|
||||
active_profile_id: std::option::Option::Some(active_profile_id),
|
||||
@@ -86,7 +130,25 @@ fn resolve_logging_startup(management: &ksp_config_lib::ConfigManagement) -> Log
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||
};
|
||||
let resolved = management.engine().load_resolved_logging_config(std::option::Option::None, &environment);
|
||||
let composite = crate::load_solprices_desk_composite(management);
|
||||
let composite = match composite {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||
};
|
||||
let logging_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_LOGGING, ksp_config_lib::FILE_ID_STD_LOGGING);
|
||||
let logging_profile = match logging_profile {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||
};
|
||||
let offchain_profile = crate::required_composite_component_profile(
|
||||
&composite,
|
||||
crate::COMPOSITE_COMPONENT_ID_OFFCHAIN_TRANSPORT,
|
||||
ksp_config_lib::FILE_ID_STD_OFFCHAIN_TRANSPORT,
|
||||
);
|
||||
if let std::result::Result::Err(error) = offchain_profile {
|
||||
return fallback_startup_plan(error);
|
||||
}
|
||||
let resolved = management.engine().resolve_logging_config_profile(&logging_profile, &environment);
|
||||
let resolved = match resolved {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return fallback_startup_plan(error),
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
// file: crates/ksp-app-solprices-desk/src/constants.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Application-owned tracing targets and domains.
|
||||
//! Application-owned composition identifiers, tracing targets and domains.
|
||||
|
||||
/// Composite-local identifier for the standard Logging component.
|
||||
pub(crate) const COMPOSITE_COMPONENT_ID_LOGGING: &str = "logging";
|
||||
/// Composite-local identifier for the standard Off-chain Transport component.
|
||||
pub(crate) const COMPOSITE_COMPONENT_ID_OFFCHAIN_TRANSPORT: &str = "offchain_transport";
|
||||
/// Structured domain used while bootstrapping Config and Logging.
|
||||
pub(crate) const TRACING_DOMAIN_BOOTSTRAP: &str = "solprices.bootstrap";
|
||||
/// Structured domain used by technical frontend events.
|
||||
pub(crate) const TRACING_DOMAIN_FRONTEND: &str = "frontend";
|
||||
/// Structured domain used by the Config-selected Off-chain Transport runtime.
|
||||
pub(crate) const TRACING_DOMAIN_OFFCHAIN_TRANSPORT: &str = "solprices.offchain_transport";
|
||||
/// Structured domain used by the SOL Prices Desk shell.
|
||||
pub(crate) const TRACING_DOMAIN_SHELL: &str = "solprices.shell";
|
||||
/// Structured domain used by Tauri window lifecycle operations.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// file: crates/ksp-app-solprices-desk/src/dto_common.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Common Tauri DTOs shared by the SOL Prices Desk scaffold.
|
||||
//! Common Tauri DTOs shared by the SOL Prices Desk bootstrap shell.
|
||||
|
||||
use ts_rs::TS; // rust-rules: trait-import
|
||||
|
||||
@@ -18,7 +18,7 @@ pub(crate) struct CommandErrorDto {
|
||||
pub(crate) message: String,
|
||||
}
|
||||
|
||||
impl CommandErrorDto {
|
||||
impl crate::CommandErrorDto {
|
||||
/// Builds a bounded safe projection from a KSP error.
|
||||
#[must_use]
|
||||
pub(crate) fn from_error(error: &ksp_core_lib::Error) -> Self {
|
||||
@@ -30,20 +30,24 @@ impl CommandErrorDto {
|
||||
}
|
||||
}
|
||||
|
||||
/// Safe scaffold/runtime snapshot exposed to the SOL Prices Desk shell before Off-chain bootstrap exists.
|
||||
/// Safe Config/bootstrap snapshot exposed before market-price provider rows exist.
|
||||
#[derive(Clone, Debug, serde::Serialize, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export, export_to = "../frontend/ts/bindings/ksp_app_solprices_desk/dto_common/RuntimeStatusDto.ts")]
|
||||
pub(crate) struct RuntimeStatusDto {
|
||||
/// Cargo application version.
|
||||
pub(crate) application_version: String,
|
||||
/// SOL Prices Desk composite profile selected for this launch.
|
||||
pub(crate) active_composite_profile: String,
|
||||
/// Active configured Logging profile, or `None` while transient fallback Logging is active.
|
||||
pub(crate) active_logging_profile: std::option::Option<String>,
|
||||
/// Number of logical Config resources registered by the current scaffold runtime.
|
||||
/// Standard Off-chain Transport profile selected by the active composite.
|
||||
pub(crate) active_offchain_profile: String,
|
||||
/// Cargo application version.
|
||||
pub(crate) application_version: String,
|
||||
/// Number of logical Config resources registered by the current runtime.
|
||||
pub(crate) config_document_count: u32,
|
||||
/// Whether SOL Prices Desk had to install its transient in-memory Logging fallback.
|
||||
pub(crate) fallback_logging_active: bool,
|
||||
/// Current implementation phase exposed by the scaffold shell.
|
||||
/// Current implementation phase exposed by the bootstrap shell.
|
||||
pub(crate) shell_phase: String,
|
||||
/// Safe startup diagnostic that caused fallback Logging, when applicable.
|
||||
pub(crate) startup_diagnostic: std::option::Option<CommandErrorDto>,
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
// file: crates/ksp-app-solprices-desk/src/errors.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Application-local error codes for SOL Prices Desk scaffold and desktop runtime surfaces.
|
||||
//! Application-local error codes for SOL Prices Desk composition and desktop runtime surfaces.
|
||||
|
||||
/// Shared SOL Prices Desk application state is internally inconsistent.
|
||||
pub(crate) const ERROR_CODE_APP_STATE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("solprices_desk", "app_state_invalid");
|
||||
/// Shared SOL Prices Desk runtime state cannot be locked safely.
|
||||
pub(crate) const ERROR_CODE_APP_STATE_LOCK_FAILED: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("solprices_desk", "app_state_lock_failed");
|
||||
/// The SOL Prices Desk Config composite is missing or misroutes one required standard component.
|
||||
pub(crate) const ERROR_CODE_CONFIG_COMPOSITE_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("solprices_desk", "config_composite_invalid");
|
||||
/// Frontend logging requested an unsupported level.
|
||||
pub(crate) const ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID: ksp_core_lib::ErrorCode = ksp_core_lib::ErrorCode::new("solprices_desk", "frontend_log_level_invalid");
|
||||
/// Frontend logging requested a target outside the application whitelist.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// file: crates/ksp-app-solprices-desk/src/lib.rs
|
||||
// version: 1
|
||||
// version: 2
|
||||
|
||||
//! Tauri desktop application scaffold for provider-neutral SOL/USD price visualization.
|
||||
|
||||
@@ -14,6 +14,7 @@ mod dto_common;
|
||||
mod errors;
|
||||
mod frontend_logging;
|
||||
mod logging_runtime;
|
||||
mod offchain_runtime;
|
||||
mod splash;
|
||||
mod tauri;
|
||||
mod tw_main;
|
||||
@@ -30,10 +31,20 @@ pub(crate) use self::bootstrap::LoggingStartup;
|
||||
pub(crate) use self::bootstrap::config_management;
|
||||
/// Initializes the scaffold Logging runtime from Config, with a bounded in-memory fallback.
|
||||
pub(crate) use self::bootstrap::initialize_logging;
|
||||
/// Loads the SOL Prices Desk composite selected by its registered logical file identifier.
|
||||
pub(crate) use self::bootstrap::load_solprices_desk_composite;
|
||||
/// Resolves one required standard Config profile from the SOL Prices Desk composite.
|
||||
pub(crate) use self::bootstrap::required_composite_component_profile;
|
||||
/// Composite-local identifier for the standard Logging component.
|
||||
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_LOGGING;
|
||||
/// Composite-local identifier for the standard Off-chain Transport component.
|
||||
pub(crate) use self::constants::COMPOSITE_COMPONENT_ID_OFFCHAIN_TRANSPORT;
|
||||
/// Structured domain used while bootstrapping Config and Logging.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_BOOTSTRAP;
|
||||
/// Structured domain used by technical frontend events.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_FRONTEND;
|
||||
/// Structured domain used by the Config-selected Off-chain Transport runtime.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_OFFCHAIN_TRANSPORT;
|
||||
/// Structured domain used by the SOL Prices Desk shell.
|
||||
pub(crate) use self::constants::TRACING_DOMAIN_SHELL;
|
||||
/// Structured domain used by Tauri window lifecycle operations.
|
||||
@@ -54,6 +65,8 @@ pub(crate) use self::dto_common::RuntimeStatusDto;
|
||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_INVALID;
|
||||
/// Shared SOL Prices Desk runtime state cannot be locked safely.
|
||||
pub(crate) use self::errors::ERROR_CODE_APP_STATE_LOCK_FAILED;
|
||||
/// The SOL Prices Desk Config composite is missing or misroutes a required component.
|
||||
pub(crate) use self::errors::ERROR_CODE_CONFIG_COMPOSITE_INVALID;
|
||||
/// Frontend logging requested an unsupported level.
|
||||
pub(crate) use self::errors::ERROR_CODE_FRONTEND_LOG_LEVEL_INVALID;
|
||||
/// Frontend logging requested a target outside the application whitelist.
|
||||
@@ -76,6 +89,10 @@ pub(crate) use self::frontend_logging::FrontendLogPayloadDto;
|
||||
pub(crate) use self::frontend_logging::emit_frontend_log_event;
|
||||
/// Creates the stable runtime identity for this SOL Prices Desk process launch.
|
||||
pub(crate) use self::logging_runtime::launch_identity;
|
||||
/// Resolved composite and Off-chain Transport configuration retained by SOL Prices Desk.
|
||||
pub(crate) use self::offchain_runtime::OffchainTransportStartup;
|
||||
/// Resolves the composite-selected Off-chain Transport profile and constructs the Config-owned service.
|
||||
pub(crate) use self::offchain_runtime::initialize_offchain_transport;
|
||||
/// Command emitted by Rust to the splash frontend.
|
||||
pub(crate) use self::splash::SplashOrderDto;
|
||||
/// Runtime timings used by the common desk splash lifecycle.
|
||||
|
||||
59
crates/ksp-app-solprices-desk/src/offchain_runtime.rs
Normal file
59
crates/ksp-app-solprices-desk/src/offchain_runtime.rs
Normal file
@@ -0,0 +1,59 @@
|
||||
// file: crates/ksp-app-solprices-desk/src/offchain_runtime.rs
|
||||
// version: 1
|
||||
|
||||
//! Config-owned Off-chain Transport bootstrap for SOL Prices Desk.
|
||||
|
||||
/// Resolved SOL Prices Desk composite and Off-chain Transport configuration retained by application state.
|
||||
pub(crate) struct OffchainTransportStartup {
|
||||
composite_profile_id: String,
|
||||
resolved: ksp_config_lib::ResolvedOffchainTransportConfig,
|
||||
}
|
||||
|
||||
impl crate::OffchainTransportStartup {
|
||||
/// Returns the composite profile selected for this application launch.
|
||||
#[must_use]
|
||||
pub(crate) fn composite_profile_id(&self) -> &str {
|
||||
return self.composite_profile_id.as_str();
|
||||
}
|
||||
|
||||
/// Returns the Config-resolved Off-chain Transport runtime configuration.
|
||||
#[must_use]
|
||||
pub(crate) const fn resolved(&self) -> &ksp_config_lib::ResolvedOffchainTransportConfig {
|
||||
return &self.resolved;
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the composite-selected Off-chain Transport profile and constructs the provider-neutral service owned by Config.
|
||||
pub(crate) fn initialize_offchain_transport(management: &ksp_config_lib::ConfigManagement) -> ksp_core_lib::Result<crate::OffchainTransportStartup> {
|
||||
let environment = ksp_config_lib::ConfigEnvironment::load();
|
||||
let environment = match environment {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let composite = crate::load_solprices_desk_composite(management);
|
||||
let composite = match composite {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let logging_profile = crate::required_composite_component_profile(&composite, crate::COMPOSITE_COMPONENT_ID_LOGGING, ksp_config_lib::FILE_ID_STD_LOGGING);
|
||||
if let std::result::Result::Err(error) = logging_profile {
|
||||
return std::result::Result::Err(error);
|
||||
}
|
||||
let offchain_profile = crate::required_composite_component_profile(
|
||||
&composite,
|
||||
crate::COMPOSITE_COMPONENT_ID_OFFCHAIN_TRANSPORT,
|
||||
ksp_config_lib::FILE_ID_STD_OFFCHAIN_TRANSPORT,
|
||||
);
|
||||
let offchain_profile = match offchain_profile {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let resolved = management.engine().resolve_offchain_transport_config_profile(&offchain_profile, &environment);
|
||||
let resolved = match resolved {
|
||||
std::result::Result::Ok(value) => value,
|
||||
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
||||
};
|
||||
let provider_count = resolved.service().registry().len();
|
||||
ksp_logging_lib::info!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_OFFCHAIN_TRANSPORT, composite_profile = composite.profile_id(), offchain_profile = resolved.profile_id(), provider_count, "initialized SOL Prices Desk Off-chain Transport from composite-managed configuration");
|
||||
return std::result::Result::Ok(crate::OffchainTransportStartup { composite_profile_id: composite.profile_id().to_owned(), resolved });
|
||||
}
|
||||
Reference in New Issue
Block a user