making linters happy
Some checks failed
CI / Rust Blocking Checks (push) Failing after 22s
CI / Web Blocking Checks (push) Failing after 26s
CI / Security Blocking Checks (push) Successful in 9s
CI / Web Advisory Checks (push) Successful in 32s
CI / Security Advisory Checks (push) Has been cancelled

This commit is contained in:
2026-03-04 23:44:45 -06:00
parent 6a5a3c2b78
commit 13749409cd
81 changed files with 468 additions and 599 deletions

View File

@@ -107,7 +107,7 @@ impl HistoryQueryParams {
pub fn to_repo_params(
&self,
) -> attune_common::repositories::entity_history::HistoryQueryParams {
let limit = (self.page_size.min(1000).max(1)) as i64;
let limit = (self.page_size.clamp(1, 1000)) as i64;
let offset = ((self.page.saturating_sub(1)) as i64) * limit;
attune_common::repositories::entity_history::HistoryQueryParams {

View File

@@ -160,7 +160,10 @@ pub async fn create_action(
request.validate()?;
// Check if action with same ref already exists
if let Some(_) = ActionRepository::find_by_ref(&state.db, &request.r#ref).await? {
if ActionRepository::find_by_ref(&state.db, &request.r#ref)
.await?
.is_some()
{
return Err(ApiError::Conflict(format!(
"Action with ref '{}' already exists",
request.r#ref

View File

@@ -1877,7 +1877,7 @@ pub async fn stream_artifact(
Some((
Ok(Event::default()
.event("content")
.data(String::from_utf8_lossy(&buf).into_owned())),
.data(String::from_utf8_lossy(&buf))),
TailState::Tailing {
full_path,
file_path,
@@ -1967,7 +1967,7 @@ pub async fn stream_artifact(
Some((
Ok(Event::default()
.event("append")
.data(String::from_utf8_lossy(&new_buf).into_owned())),
.data(String::from_utf8_lossy(&new_buf))),
TailState::Tailing {
full_path,
file_path,

View File

@@ -158,7 +158,10 @@ pub async fn register(
.map_err(|e| ApiError::ValidationError(format!("Invalid registration request: {}", e)))?;
// Check if login already exists
if let Some(_) = IdentityRepository::find_by_login(&state.db, &payload.login).await? {
if IdentityRepository::find_by_login(&state.db, &payload.login)
.await?
.is_some()
{
return Err(ApiError::Conflict(format!(
"Identity with login '{}' already exists",
payload.login

View File

@@ -138,7 +138,10 @@ pub async fn create_key(
request.validate()?;
// Check if key with same ref already exists
if let Some(_) = KeyRepository::find_by_ref(&state.db, &request.r#ref).await? {
if KeyRepository::find_by_ref(&state.db, &request.r#ref)
.await?
.is_some()
{
return Err(ApiError::Conflict(format!(
"Key with ref '{}' already exists",
request.r#ref

View File

@@ -585,10 +585,9 @@ pub async fn upload_pack(
skip_tests,
)
.await
.map_err(|e| {
.inspect_err(|_e| {
// Clean up permanent storage on failure
let _ = std::fs::remove_dir_all(&final_path);
e
})?;
// Fetch the registered pack
@@ -947,8 +946,8 @@ async fn register_pack_internal(
// a best-effort optimisation for non-Docker (bare-metal) setups
// where the API host has the interpreter available.
if let Some(ref env_cfg) = exec_config.environment {
if env_cfg.env_type != "none" {
if !env_dir.exists() && !env_cfg.create_command.is_empty() {
if env_cfg.env_type != "none"
&& !env_dir.exists() && !env_cfg.create_command.is_empty() {
// Ensure parent directories exist
if let Some(parent) = env_dir.parent() {
let _ = std::fs::create_dir_all(parent);
@@ -1002,7 +1001,6 @@ async fn register_pack_internal(
}
}
}
}
}
// Attempt to install dependencies if manifest file exists.
@@ -1107,9 +1105,7 @@ async fn register_pack_internal(
if is_new_pack {
let _ = PackRepository::delete(&state.db, pack.id).await;
}
return Err(ApiError::BadRequest(format!(
"Pack registration failed: tests did not pass. Use force=true to register anyway."
)));
return Err(ApiError::BadRequest("Pack registration failed: tests did not pass. Use force=true to register anyway.".to_string()));
}
if !test_passed && force {
@@ -1359,10 +1355,9 @@ pub async fn install_pack(
request.skip_tests,
)
.await
.map_err(|e| {
.inspect_err(|_e| {
// Clean up the permanent storage if registration fails
let _ = std::fs::remove_dir_all(&final_path);
e
})?;
// Fetch the registered pack

View File

@@ -290,7 +290,10 @@ pub async fn create_rule(
request.validate()?;
// Check if rule with same ref already exists
if let Some(_) = RuleRepository::find_by_ref(&state.db, &request.r#ref).await? {
if RuleRepository::find_by_ref(&state.db, &request.r#ref)
.await?
.is_some()
{
return Err(ApiError::Conflict(format!(
"Rule with ref '{}' already exists",
request.r#ref

View File

@@ -198,7 +198,10 @@ pub async fn create_trigger(
request.validate()?;
// Check if trigger with same ref already exists
if let Some(_) = TriggerRepository::find_by_ref(&state.db, &request.r#ref).await? {
if TriggerRepository::find_by_ref(&state.db, &request.r#ref)
.await?
.is_some()
{
return Err(ApiError::Conflict(format!(
"Trigger with ref '{}' already exists",
request.r#ref
@@ -623,7 +626,10 @@ pub async fn create_sensor(
request.validate()?;
// Check if sensor with same ref already exists
if let Some(_) = SensorRepository::find_by_ref(&state.db, &request.r#ref).await? {
if SensorRepository::find_by_ref(&state.db, &request.r#ref)
.await?
.is_some()
{
return Err(ApiError::Conflict(format!(
"Sensor with ref '{}' already exists",
request.r#ref

View File

@@ -714,6 +714,7 @@ pub async fn receive_webhook(
}
// Helper function to log webhook events
#[allow(clippy::too_many_arguments)]
async fn log_webhook_event(
state: &AppState,
trigger: &attune_common::models::trigger::Trigger,
@@ -753,6 +754,7 @@ async fn log_webhook_event(
}
// Helper function to log failures when trigger is not found
#[allow(clippy::too_many_arguments)]
async fn log_webhook_failure(
_state: &AppState,
webhook_key: String,

View File

@@ -181,7 +181,10 @@ pub async fn create_workflow(
request.validate()?;
// Check if workflow with same ref already exists
if let Some(_) = WorkflowDefinitionRepository::find_by_ref(&state.db, &request.r#ref).await? {
if WorkflowDefinitionRepository::find_by_ref(&state.db, &request.r#ref)
.await?
.is_some()
{
return Err(ApiError::Conflict(format!(
"Workflow with ref '{}' already exists",
request.r#ref
@@ -519,7 +522,7 @@ pub async fn update_workflow_file(
/// Write a workflow definition to disk as YAML
async fn write_workflow_yaml(
packs_base_dir: &PathBuf,
packs_base_dir: &std::path::Path,
pack_ref: &str,
request: &SaveWorkflowFileRequest,
) -> Result<(), ApiError> {
@@ -630,9 +633,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
"# Action definition for workflow {}.{}",
pack_ref, request.name
));
lines.push(format!(
"# The workflow graph (tasks, transitions, variables) is in:"
));
lines.push("# The workflow graph (tasks, transitions, variables) is in:".to_string());
lines.push(format!(
"# actions/workflows/{}.workflow.yaml",
request.name
@@ -646,7 +647,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
lines.push(format!("description: \"{}\"", desc.replace('"', "\\\"")));
}
}
lines.push(format!("enabled: true"));
lines.push("enabled: true".to_string());
lines.push(format!(
"workflow_file: workflows/{}.workflow.yaml",
request.name
@@ -658,7 +659,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
if !obj.is_empty() {
lines.push(String::new());
let params_yaml = serde_yaml_ng::to_string(params).unwrap_or_default();
lines.push(format!("parameters:"));
lines.push("parameters:".to_string());
// Indent the YAML output under `parameters:`
for line in params_yaml.lines() {
lines.push(format!(" {}", line));
@@ -673,7 +674,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
if !obj.is_empty() {
lines.push(String::new());
let output_yaml = serde_yaml_ng::to_string(output).unwrap_or_default();
lines.push(format!("output:"));
lines.push("output:".to_string());
for line in output_yaml.lines() {
lines.push(format!(" {}", line));
}
@@ -685,7 +686,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
if let Some(ref tags) = request.tags {
if !tags.is_empty() {
lines.push(String::new());
lines.push(format!("tags:"));
lines.push("tags:".to_string());
for tag in tags {
lines.push(format!(" - {}", tag));
}
@@ -701,6 +702,7 @@ fn build_action_yaml(pack_ref: &str, request: &SaveWorkflowFileRequest) -> Strin
/// This ensures the workflow appears in action lists and the action palette in the
/// workflow builder. The action is linked to the workflow definition via the
/// `workflow_def` FK.
#[allow(clippy::too_many_arguments)]
async fn create_companion_action(
db: &sqlx::PgPool,
workflow_ref: &str,
@@ -835,6 +837,7 @@ async fn update_companion_action(
///
/// If the action already exists, update it. If it doesn't exist (e.g., for workflows
/// created before the companion-action fix), create it.
#[allow(clippy::too_many_arguments)]
async fn ensure_companion_action(
db: &sqlx::PgPool,
workflow_def_id: i64,

View File

@@ -362,11 +362,11 @@ impl Drop for TestContext {
let test_packs_dir = self.test_packs_dir.clone();
// Spawn cleanup task in background
let _ = tokio::spawn(async move {
drop(tokio::spawn(async move {
if let Err(e) = cleanup_test_schema(&schema).await {
eprintln!("Failed to cleanup test schema {}: {}", schema, e);
}
});
}));
// Cleanup the test packs directory synchronously
let _ = std::fs::remove_dir_all(&test_packs_dir);

View File

@@ -64,7 +64,7 @@ async fn test_sync_pack_workflows_endpoint() {
// Use unique pack name to avoid conflicts in parallel tests
let pack_name = format!(
"test_pack_{}",
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
);
// Create temporary directory for pack workflows
@@ -100,7 +100,7 @@ async fn test_validate_pack_workflows_endpoint() {
// Use unique pack name to avoid conflicts in parallel tests
let pack_name = format!(
"test_pack_{}",
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
);
// Create pack in database
@@ -158,7 +158,7 @@ async fn test_sync_workflows_requires_authentication() {
// Use unique pack name to avoid conflicts in parallel tests
let pack_name = format!(
"test_pack_{}",
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
);
// Create pack in database
@@ -185,7 +185,7 @@ async fn test_validate_workflows_requires_authentication() {
// Use unique pack name to avoid conflicts in parallel tests
let pack_name = format!(
"test_pack_{}",
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
);
// Create pack in database

View File

@@ -14,7 +14,7 @@ use helpers::*;
fn unique_pack_name() -> String {
format!(
"test_pack_{}",
uuid::Uuid::new_v4().to_string().replace("-", "")[..8].to_string()
&uuid::Uuid::new_v4().to_string().replace("-", "")[..8]
)
}

View File

@@ -180,8 +180,8 @@ impl ApiClient {
let req = self.attach_body(self.build_request(method.clone(), path), body);
let response = req.send().await.context("Failed to send request to API")?;
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some() {
if self.refresh_auth_token().await? {
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some()
&& self.refresh_auth_token().await? {
// Retry with new token
let req = self.attach_body(self.build_request(method, path), body);
let response = req
@@ -190,7 +190,6 @@ impl ApiClient {
.context("Failed to send request to API (retry)")?;
return self.handle_response(response).await;
}
}
self.handle_response(response).await
}
@@ -205,8 +204,8 @@ impl ApiClient {
let req = self.attach_body(self.build_request(method.clone(), path), body);
let response = req.send().await.context("Failed to send request to API")?;
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some() {
if self.refresh_auth_token().await? {
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some()
&& self.refresh_auth_token().await? {
let req = self.attach_body(self.build_request(method, path), body);
let response = req
.send()
@@ -214,7 +213,6 @@ impl ApiClient {
.context("Failed to send request to API (retry)")?;
return self.handle_empty_response(response).await;
}
}
self.handle_empty_response(response).await
}
@@ -393,8 +391,8 @@ impl ApiClient {
.await
.context("Failed to send multipart request to API")?;
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some() {
if self.refresh_auth_token().await? {
if response.status() == StatusCode::UNAUTHORIZED && self.refresh_token.is_some()
&& self.refresh_auth_token().await? {
// Retry with new token
let req = build_multipart_request(self, &file_bytes)?;
let response = req
@@ -403,7 +401,6 @@ impl ApiClient {
.context("Failed to send multipart request to API (retry)")?;
return self.handle_response(response).await;
}
}
self.handle_response(response).await
}

View File

@@ -314,6 +314,7 @@ async fn handle_show(
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn handle_update(
action_ref: String,
label: Option<String>,
@@ -415,6 +416,7 @@ async fn handle_delete(
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn handle_execute(
action_ref: String,
params: Vec<String>,
@@ -454,11 +456,8 @@ async fn handle_execute(
parameters,
};
match output_format {
OutputFormat::Table => {
output::print_info(&format!("Executing action: {}", action_ref));
}
_ => {}
if output_format == OutputFormat::Table {
output::print_info(&format!("Executing action: {}", action_ref));
}
let path = "/executions/execute".to_string();
@@ -481,14 +480,11 @@ async fn handle_execute(
return Ok(());
}
match output_format {
OutputFormat::Table => {
output::print_info(&format!(
"Waiting for execution {} to complete...",
execution.id
));
}
_ => {}
if output_format == OutputFormat::Table {
output::print_info(&format!(
"Waiting for execution {} to complete...",
execution.id
));
}
let verbose = matches!(output_format, OutputFormat::Table);

View File

@@ -163,6 +163,7 @@ pub async fn handle_execution_command(
}
}
#[allow(clippy::too_many_arguments)]
async fn handle_list(
profile: &Option<String>,
pack: Option<String>,

View File

@@ -468,7 +468,7 @@ pub async fn handle_pack_command(
///
/// Splits on `_`, `-`, or `.` and title-cases each word.
fn label_from_ref(r: &str) -> String {
r.split(|c| c == '_' || c == '-' || c == '.')
r.split(['_', '-', '.'])
.filter(|s| !s.is_empty())
.map(|word| {
let mut chars = word.chars();
@@ -484,6 +484,7 @@ fn label_from_ref(r: &str) -> String {
.join(" ")
}
#[allow(clippy::too_many_arguments)]
async fn handle_create(
profile: &Option<String>,
ref_flag: Option<String>,
@@ -725,6 +726,7 @@ async fn handle_show(
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn handle_install(
profile: &Option<String>,
source: String,
@@ -742,18 +744,15 @@ async fn handle_install(
// Detect source type
let source_type = detect_source_type(&source, ref_spec.as_deref(), no_registry);
match output_format {
OutputFormat::Table => {
output::print_info(&format!(
"Installing pack from: {} ({})",
source, source_type
));
output::print_info("Starting installation...");
if skip_deps {
output::print_info("⚠ Dependency validation will be skipped");
}
if output_format == OutputFormat::Table {
output::print_info(&format!(
"Installing pack from: {} ({})",
source, source_type
));
output::print_info("Starting installation...");
if skip_deps {
output::print_info("⚠ Dependency validation will be skipped");
}
_ => {}
}
let request = InstallPackRequest {
@@ -880,12 +879,9 @@ async fn handle_upload(
.and_then(|v| v.as_str())
.unwrap_or("unknown");
match output_format {
OutputFormat::Table => {
output::print_info(&format!("Uploading pack '{}' from: {}", pack_ref, path));
output::print_info("Creating archive...");
}
_ => {}
if output_format == OutputFormat::Table {
output::print_info(&format!("Uploading pack '{}' from: {}", pack_ref, path));
output::print_info("Creating archive...");
}
// Build an in-memory tar.gz of the pack directory
@@ -908,14 +904,11 @@ async fn handle_upload(
let archive_size_kb = tar_gz_bytes.len() / 1024;
match output_format {
OutputFormat::Table => {
output::print_info(&format!(
"Archive ready ({} KB), uploading...",
archive_size_kb
));
}
_ => {}
if output_format == OutputFormat::Table {
output::print_info(&format!(
"Archive ready ({} KB), uploading...",
archive_size_kb
));
}
let config = CliConfig::load_with_profile(profile.as_deref())?;
@@ -1014,25 +1007,17 @@ async fn handle_register(
&& !path.starts_with("/app/")
&& !path.starts_with("/packs");
if looks_local {
match output_format {
OutputFormat::Table => {
output::print_info(&format!("Registering pack from: {}", path));
eprintln!(
"⚠ Warning: '{}' looks like a local path. If the API is running in \
Docker it may not be able to access this path.\n \
Use `attune pack upload {}` instead to upload the pack directly.",
path, path
);
}
_ => {}
}
} else {
match output_format {
OutputFormat::Table => {
output::print_info(&format!("Registering pack from: {}", path));
}
_ => {}
if output_format == OutputFormat::Table {
output::print_info(&format!("Registering pack from: {}", path));
eprintln!(
"⚠ Warning: '{}' looks like a local path. If the API is running in \
Docker it may not be able to access this path.\n \
Use `attune pack upload {}` instead to upload the pack directly.",
path, path
);
}
} else if output_format == OutputFormat::Table {
output::print_info(&format!("Registering pack from: {}", path));
}
let request = RegisterPackRequest {
@@ -1173,13 +1158,10 @@ async fn handle_test(
let executor = TestExecutor::new(pack_base_dir);
// Print test start message
match output_format {
OutputFormat::Table => {
println!();
output::print_section(&format!("🧪 Testing Pack: {} v{}", pack_ref, pack_version));
println!();
}
_ => {}
if output_format == OutputFormat::Table {
println!();
output::print_section(&format!("🧪 Testing Pack: {} v{}", pack_ref, pack_version));
println!();
}
// Execute tests
@@ -1688,7 +1670,7 @@ async fn handle_index_entry(
if let Some(ref git) = git_url {
let default_ref = format!("v{}", version);
let ref_value = git_ref.as_ref().map(|s| s.as_str()).unwrap_or(&default_ref);
let ref_value = git_ref.as_deref().unwrap_or(&default_ref);
let git_source = serde_json::json!({
"type": "git",
"url": git,
@@ -1790,6 +1772,7 @@ async fn handle_index_entry(
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn handle_update(
profile: &Option<String>,
pack_ref: String,

View File

@@ -76,10 +76,8 @@ pub async fn handle_index_update(
if output_format == OutputFormat::Table {
output::print_info(&format!("Updating existing entry for '{}'", pack_ref));
}
} else {
if output_format == OutputFormat::Table {
output::print_info(&format!("Adding new entry for '{}'", pack_ref));
}
} else if output_format == OutputFormat::Table {
output::print_info(&format!("Adding new entry for '{}'", pack_ref));
}
// Calculate checksum
@@ -93,7 +91,7 @@ pub async fn handle_index_update(
if let Some(ref git) = git_url {
let default_ref = format!("v{}", version);
let ref_value = git_ref.as_ref().map(|s| s.as_str()).unwrap_or(&default_ref);
let ref_value = git_ref.as_deref().unwrap_or(&default_ref);
install_sources.push(serde_json::json!({
"type": "git",
"url": git,
@@ -318,13 +316,11 @@ pub async fn handle_index_merge(
));
}
packs_map.insert(pack_ref.to_string(), pack.clone());
} else {
if output_format == OutputFormat::Table {
output::print_info(&format!(
" Keeping '{}' at {} (newer than {})",
pack_ref, existing_version, new_version
));
}
} else if output_format == OutputFormat::Table {
output::print_info(&format!(
" Keeping '{}' at {} (newer than {})",
pack_ref, existing_version, new_version
));
}
duplicates_resolved += 1;
} else {

View File

@@ -354,6 +354,7 @@ async fn handle_show(
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn handle_update(
profile: &Option<String>,
rule_ref: String,
@@ -477,6 +478,7 @@ async fn handle_toggle(
Ok(())
}
#[allow(clippy::too_many_arguments)]
async fn handle_create(
profile: &Option<String>,
name: String,

View File

@@ -472,7 +472,7 @@ fn resolve_ws_url(opts: &WaitOptions<'_>) -> Option<String> {
let api_url = opts.api_client.base_url();
// Transform http(s)://host:PORT/... → ws(s)://host:8081
let ws_url = derive_notifier_url(&api_url)?;
let ws_url = derive_notifier_url(api_url)?;
Some(ws_url)
}

View File

@@ -192,7 +192,7 @@ fn test_pack_index_entry_generates_valid_json() {
// Verify metadata
assert_eq!(json["author"], "Test Author");
assert_eq!(json["license"], "Apache-2.0");
assert!(json["keywords"].as_array().unwrap().len() > 0);
assert!(!json["keywords"].as_array().unwrap().is_empty());
}
#[test]
@@ -212,7 +212,7 @@ fn test_pack_index_entry_with_archive_url() {
let stdout = String::from_utf8(output.get_output().stdout.clone()).unwrap();
let json: Value = serde_json::from_str(&stdout).unwrap();
assert!(json["install_sources"].as_array().unwrap().len() > 0);
assert!(!json["install_sources"].as_array().unwrap().is_empty());
let archive_source = &json["install_sources"][0];
assert_eq!(archive_source["type"], "archive");

View File

@@ -45,21 +45,16 @@ pub struct Claims {
pub metadata: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum TokenType {
#[default]
Access,
Refresh,
Sensor,
Execution,
}
impl Default for TokenType {
fn default() -> Self {
Self::Access
}
}
/// Configuration for JWT tokens
#[derive(Debug, Clone)]
pub struct JwtConfig {
@@ -247,11 +242,7 @@ pub fn validate_token(token: &str, config: &JwtConfig) -> Result<Claims, JwtErro
/// Extract token from Authorization header
pub fn extract_token_from_header(auth_header: &str) -> Option<&str> {
if auth_header.starts_with("Bearer ") {
Some(&auth_header[7..])
} else {
None
}
auth_header.strip_prefix("Bearer ")
}
#[cfg(test)]

View File

@@ -45,21 +45,16 @@ pub mod enums {
use utoipa::ToSchema;
/// How parameters should be delivered to an action
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum ParameterDelivery {
/// Pass parameters via stdin (secure, recommended for most cases)
#[default]
Stdin,
/// Pass parameters via temporary file (secure, best for large payloads)
File,
}
impl Default for ParameterDelivery {
fn default() -> Self {
Self::Stdin
}
}
impl fmt::Display for ParameterDelivery {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@@ -99,31 +94,23 @@ pub mod enums {
&self,
buf: &mut sqlx::postgres::PgArgumentBuffer,
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
Ok(<String as sqlx::Encode<sqlx::Postgres>>::encode(
self.to_string(),
buf,
)?)
<String as sqlx::Encode<sqlx::Postgres>>::encode(self.to_string(), buf)
}
}
/// Format for parameter serialization
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum ParameterFormat {
/// KEY='VALUE' format (one per line)
Dotenv,
/// JSON object
#[default]
Json,
/// YAML format
Yaml,
}
impl Default for ParameterFormat {
fn default() -> Self {
Self::Json
}
}
impl fmt::Display for ParameterFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@@ -165,18 +152,16 @@ pub mod enums {
&self,
buf: &mut sqlx::postgres::PgArgumentBuffer,
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
Ok(<String as sqlx::Encode<sqlx::Postgres>>::encode(
self.to_string(),
buf,
)?)
<String as sqlx::Encode<sqlx::Postgres>>::encode(self.to_string(), buf)
}
}
/// Format for action output parsing
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum OutputFormat {
/// Plain text (no parsing)
#[default]
Text,
/// Parse as JSON
Json,
@@ -186,12 +171,6 @@ pub mod enums {
Jsonl,
}
impl Default for OutputFormat {
fn default() -> Self {
Self::Text
}
}
impl fmt::Display for OutputFormat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
@@ -235,10 +214,7 @@ pub mod enums {
&self,
buf: &mut sqlx::postgres::PgArgumentBuffer,
) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
Ok(<String as sqlx::Encode<sqlx::Postgres>>::encode(
self.to_string(),
buf,
)?)
<String as sqlx::Encode<sqlx::Postgres>>::encode(self.to_string(), buf)
}
}
@@ -371,20 +347,17 @@ pub mod enums {
/// - `Public`: viewable by all authenticated users on the platform.
/// - `Private`: restricted based on the artifact's `scope` and `owner` fields.
/// Full RBAC enforcement is deferred; for now the field enables filtering.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Type, ToSchema)]
#[derive(
Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize, Type, ToSchema,
)]
#[sqlx(type_name = "artifact_visibility_enum", rename_all = "lowercase")]
#[serde(rename_all = "lowercase")]
pub enum ArtifactVisibility {
Public,
#[default]
Private,
}
impl Default for ArtifactVisibility {
fn default() -> Self {
Self::Private
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Type, ToSchema)]
#[sqlx(type_name = "workflow_task_status_enum", rename_all = "lowercase")]
#[serde(rename_all = "lowercase")]

View File

@@ -141,7 +141,7 @@ mod tests {
fn test_message_queue_creation() {
// This test just verifies the struct can be instantiated
// Actual connection tests require a running RabbitMQ instance
assert!(true);
// (nothing to assert without a live broker)
}
#[tokio::test]

View File

@@ -69,20 +69,15 @@ use serde::{Deserialize, Serialize};
use std::fmt;
/// Message delivery mode
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum DeliveryMode {
/// Non-persistent messages (faster, but may be lost on broker restart)
NonPersistent = 1,
/// Persistent messages (slower, but survive broker restart)
#[default]
Persistent = 2,
}
impl Default for DeliveryMode {
fn default() -> Self {
Self::Persistent
}
}
/// Message priority (0-9, higher is more urgent)
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub struct Priority(u8);
@@ -125,25 +120,21 @@ impl fmt::Display for Priority {
}
/// Message acknowledgment mode
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum AckMode {
/// Automatically acknowledge messages after delivery
Auto,
/// Manually acknowledge messages after processing
#[default]
Manual,
}
impl Default for AckMode {
fn default() -> Self {
Self::Manual
}
}
/// Exchange type
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum ExchangeType {
/// Direct exchange - routes messages with exact routing key match
#[default]
Direct,
/// Topic exchange - routes messages using pattern matching
Topic,
@@ -165,12 +156,6 @@ impl ExchangeType {
}
}
impl Default for ExchangeType {
fn default() -> Self {
Self::Direct
}
}
impl fmt::Display for ExchangeType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())

View File

@@ -41,7 +41,7 @@ impl PackEnvironmentStatus {
}
}
pub fn from_str(s: &str) -> Option<Self> {
pub fn parse_status(s: &str) -> Option<Self> {
match s {
"pending" => Some(Self::Pending),
"installing" => Some(Self::Installing),
@@ -194,7 +194,7 @@ impl PackEnvironmentManager {
if let Some(row) = row {
let status_str: String = row.try_get("status")?;
let status = PackEnvironmentStatus::from_str(&status_str)
let status = PackEnvironmentStatus::parse_status(&status_str)
.unwrap_or(PackEnvironmentStatus::Failed);
Ok(Some(PackEnvironment {
@@ -343,7 +343,7 @@ impl PackEnvironmentManager {
let mut environments = Vec::new();
for row in rows {
let status_str: String = row.try_get("status")?;
let status = PackEnvironmentStatus::from_str(&status_str)
let status = PackEnvironmentStatus::parse_status(&status_str)
.unwrap_or(PackEnvironmentStatus::Failed);
environments.push(PackEnvironment {
@@ -452,8 +452,8 @@ impl PackEnvironmentManager {
.await?;
let status_str: String = row.try_get("status")?;
let status =
PackEnvironmentStatus::from_str(&status_str).unwrap_or(PackEnvironmentStatus::Pending);
let status = PackEnvironmentStatus::parse_status(&status_str)
.unwrap_or(PackEnvironmentStatus::Pending);
Ok(PackEnvironment {
id: row.try_get("id")?,
@@ -496,8 +496,8 @@ impl PackEnvironmentManager {
.await?;
let status_str: String = row.try_get("status")?;
let status =
PackEnvironmentStatus::from_str(&status_str).unwrap_or(PackEnvironmentStatus::Ready);
let status = PackEnvironmentStatus::parse_status(&status_str)
.unwrap_or(PackEnvironmentStatus::Ready);
Ok(PackEnvironment {
id: row.try_get("id")?,
@@ -580,7 +580,7 @@ impl PackEnvironmentManager {
Ok(output) => {
install_log.push_str(&format!("\n=== {} ===\n", action.name));
install_log.push_str(&output);
install_log.push_str("\n");
install_log.push('\n');
}
Err(e) => {
let error_msg = format!("Installer '{}' failed: {}", action.name, e);
@@ -701,20 +701,18 @@ impl PackEnvironmentManager {
.map(|obj| {
obj.iter()
.filter_map(|(k, v)| {
v.as_str()
.map(|s| {
let resolved = self
.resolve_template(
s,
pack_ref,
runtime_ref,
env_path,
&pack_path_str,
)
.ok()?;
Some((k.clone(), resolved))
})
.flatten()
v.as_str().and_then(|s| {
let resolved = self
.resolve_template(
s,
pack_ref,
runtime_ref,
env_path,
&pack_path_str,
)
.ok()?;
Some((k.clone(), resolved))
})
})
.collect::<HashMap<String, String>>()
})
@@ -877,9 +875,9 @@ mod tests {
fn test_environment_status_conversion() {
assert_eq!(PackEnvironmentStatus::Ready.as_str(), "ready");
assert_eq!(
PackEnvironmentStatus::from_str("ready"),
PackEnvironmentStatus::parse_status("ready"),
Some(PackEnvironmentStatus::Ready)
);
assert_eq!(PackEnvironmentStatus::from_str("invalid"), None);
assert_eq!(PackEnvironmentStatus::parse_status("invalid"), None);
}
}

View File

@@ -151,17 +151,15 @@ impl DependencyValidator {
};
let error = if !satisfied {
if detected_version.is_none() {
Some(format!("Runtime '{}' not found on system", runtime))
} else if let Some(ref constraint) = version_constraint {
Some(format!(
"Runtime '{}' version {} does not satisfy constraint '{}'",
runtime,
detected_version.as_ref().unwrap(),
constraint
))
if let Some(ref detected) = detected_version {
version_constraint.as_ref().map(|constraint| {
format!(
"Runtime '{}' version {} does not satisfy constraint '{}'",
runtime, detected, constraint
)
})
} else {
None
Some(format!("Runtime '{}' not found on system", runtime))
}
} else {
None
@@ -192,15 +190,13 @@ impl DependencyValidator {
};
let error = if !satisfied {
if installed_version.is_none() {
Some(format!("Required pack '{}' is not installed", pack_ref))
} else {
if let Some(ref installed) = installed_version {
Some(format!(
"Pack '{}' version {} does not satisfy constraint '{}'",
pack_ref,
installed_version.as_ref().unwrap(),
version_constraint
pack_ref, installed, version_constraint
))
} else {
Some(format!("Required pack '{}' is not installed", pack_ref))
}
} else {
None
@@ -335,30 +331,30 @@ fn match_version_constraint(version: &str, constraint: &str) -> Result<bool> {
}
// Parse constraint
if constraint.starts_with(">=") {
let required = constraint[2..].trim();
if let Some(stripped) = constraint.strip_prefix(">=") {
let required = stripped.trim();
Ok(compare_versions(version, required)? >= 0)
} else if constraint.starts_with("<=") {
let required = constraint[2..].trim();
} else if let Some(stripped) = constraint.strip_prefix("<=") {
let required = stripped.trim();
Ok(compare_versions(version, required)? <= 0)
} else if constraint.starts_with('>') {
let required = constraint[1..].trim();
} else if let Some(stripped) = constraint.strip_prefix('>') {
let required = stripped.trim();
Ok(compare_versions(version, required)? > 0)
} else if constraint.starts_with('<') {
let required = constraint[1..].trim();
} else if let Some(stripped) = constraint.strip_prefix('<') {
let required = stripped.trim();
Ok(compare_versions(version, required)? < 0)
} else if constraint.starts_with('=') {
let required = constraint[1..].trim();
} else if let Some(stripped) = constraint.strip_prefix('=') {
let required = stripped.trim();
Ok(compare_versions(version, required)? == 0)
} else if constraint.starts_with('^') {
} else if let Some(stripped) = constraint.strip_prefix('^') {
// Caret: Compatible with version (major.minor.patch)
// ^1.2.3 := >=1.2.3 <2.0.0
let required = constraint[1..].trim();
let required = stripped.trim();
match_caret_constraint(version, required)
} else if constraint.starts_with('~') {
} else if let Some(stripped) = constraint.strip_prefix('~') {
// Tilde: Approximately equivalent to version
// ~1.2.3 := >=1.2.3 <1.3.0
let required = constraint[1..].trim();
let required = stripped.trim();
match_tilde_constraint(version, required)
} else {
// Exact match

View File

@@ -171,7 +171,7 @@ impl PackInstaller {
clone_cmd.arg("--depth").arg("1");
}
clone_cmd.arg(&url).arg(&install_dir);
clone_cmd.arg(url).arg(&install_dir);
let output = clone_cmd
.output()
@@ -421,7 +421,11 @@ impl PackInstaller {
}
// Determine filename from URL
let filename = url.split('/').last().unwrap_or("archive.zip").to_string();
let filename = url
.split('/')
.next_back()
.unwrap_or("archive.zip")
.to_string();
let archive_path = self.temp_dir.join(&filename);

View File

@@ -288,17 +288,15 @@ impl<'a> PackComponentLoader<'a> {
}
Err(e) => {
// Check for unique constraint violation (race condition)
if let Error::Database(ref db_err) = e {
if let sqlx::Error::Database(ref inner) = db_err {
if inner.is_unique_violation() {
info!(
"Runtime '{}' already exists (concurrent creation), treating as update",
runtime_ref
);
loaded_refs.push(runtime_ref);
result.runtimes_updated += 1;
continue;
}
if let Error::Database(sqlx::Error::Database(ref inner)) = e {
if inner.is_unique_violation() {
info!(
"Runtime '{}' already exists (concurrent creation), treating as update",
runtime_ref
);
loaded_refs.push(runtime_ref);
result.runtimes_updated += 1;
continue;
}
}
let msg = format!("Failed to create runtime '{}': {}", runtime_ref, e);
@@ -438,16 +436,14 @@ impl<'a> PackComponentLoader<'a> {
}
Err(e) => {
// Check for unique constraint violation (race condition)
if let Error::Database(ref db_err) = e {
if let sqlx::Error::Database(ref inner) = db_err {
if inner.is_unique_violation() {
info!(
"Version '{}' for runtime '{}' already exists (concurrent), skipping",
version_str, runtime_ref
);
loaded_versions.push(version_str);
continue;
}
if let Error::Database(sqlx::Error::Database(ref inner)) = e {
if inner.is_unique_violation() {
info!(
"Version '{}' for runtime '{}' already exists (concurrent), skipping",
version_str, runtime_ref
);
loaded_versions.push(version_str);
continue;
}
}
let msg = format!(

View File

@@ -272,11 +272,6 @@ impl Checksum {
Ok(Self { algorithm, hash })
}
/// Format as "algorithm:hash"
pub fn to_string(&self) -> String {
format!("{}:{}", self.algorithm, self.hash)
}
}
impl std::fmt::Display for Checksum {

View File

@@ -295,7 +295,7 @@ impl Update for ActionRepository {
query.push(", updated = NOW() WHERE id = ");
query.push_bind(id);
query.push(&format!(" RETURNING {}", ACTION_COLUMNS));
query.push(format!(" RETURNING {}", ACTION_COLUMNS));
let action = query
.build_query_as::<Action>()
@@ -554,7 +554,6 @@ impl ActionRepository {
}
}
/// Repository for Policy operations
// ============================================================================
// Policy Repository
// ============================================================================

View File

@@ -47,7 +47,7 @@ pub struct HistoryQueryParams {
impl HistoryQueryParams {
/// Returns the effective limit, capped at 1000.
pub fn effective_limit(&self) -> i64 {
self.limit.unwrap_or(100).min(1000).max(1)
self.limit.unwrap_or(100).clamp(1, 1000)
}
/// Returns the effective offset.

View File

@@ -582,7 +582,7 @@ impl EnforcementRepository {
}
if let Some(status) = &filters.status {
push_condition!("status = ", status.clone());
push_condition!("status = ", *status);
}
if let Some(rule_id) = filters.rule {
push_condition!("rule = ", rule_id);

View File

@@ -391,7 +391,7 @@ impl ExecutionRepository {
}
if let Some(status) = &filters.status {
push_condition!("e.status = ", status.clone());
push_condition!("e.status = ", *status);
}
if let Some(action_ref) = &filters.action_ref {
push_condition!("e.action_ref = ", action_ref.clone());

View File

@@ -129,7 +129,7 @@ impl Update for IdentityRepository {
.map_err(|e| {
// Convert RowNotFound to NotFound error
if matches!(e, sqlx::Error::RowNotFound) {
return crate::Error::not_found("identity", "id", &id.to_string());
return crate::Error::not_found("identity", "id", id.to_string());
}
e.into()
})

View File

@@ -211,7 +211,7 @@ impl InquiryRepository {
}
if let Some(status) = &filters.status {
push_condition!("status = ", status.clone());
push_condition!("status = ", *status);
}
if let Some(execution_id) = filters.execution {
push_condition!("execution = ", execution_id);

View File

@@ -218,7 +218,7 @@ impl KeyRepository {
}
if let Some(ref owner_type) = filters.owner_type {
push_condition!("owner_type = ", owner_type.clone());
push_condition!("owner_type = ", *owner_type);
}
if let Some(ref owner) = filters.owner {
push_condition!("owner = ", owner.clone());

View File

@@ -408,6 +408,7 @@ impl PackRepository {
}
/// Update installation metadata for a pack
#[allow(clippy::too_many_arguments)]
pub async fn update_installation_metadata<'e, E>(
executor: E,
id: i64,

View File

@@ -239,7 +239,7 @@ impl Update for RuntimeVersionRepository {
query.push(" WHERE id = ");
query.push_bind(id);
query.push(&format!(" RETURNING {}", SELECT_COLUMNS));
query.push(format!(" RETURNING {}", SELECT_COLUMNS));
let row = query
.build_query_as::<RuntimeVersion>()

View File

@@ -276,7 +276,7 @@ impl Update for TriggerRepository {
.map_err(|e| {
// Convert RowNotFound to NotFound error
if matches!(e, sqlx::Error::RowNotFound) {
return crate::Error::not_found("trigger", "id", &id.to_string());
return crate::Error::not_found("trigger", "id", id.to_string());
}
e.into()
})?;

View File

@@ -423,14 +423,11 @@ mod tests {
"always_available": true
});
assert_eq!(
verification
.get("always_available")
.unwrap()
.as_bool()
.unwrap(),
true
);
assert!(verification
.get("always_available")
.unwrap()
.as_bool()
.unwrap());
}
#[tokio::test]

View File

@@ -106,7 +106,7 @@ impl TestExecutor {
);
match self
.execute_test_suite(&pack_dir, runner_name, runner_config)
.execute_test_suite(pack_dir, runner_name, runner_config)
.await
{
Ok(suite_result) => {
@@ -369,7 +369,7 @@ impl TestExecutor {
let total = self.extract_number(&text, "Total Tests:");
let passed = self.extract_number(&text, "Passed:");
let failed = self.extract_number(&text, "Failed:");
let skipped = self.extract_number(&text, "Skipped:").or_else(|| Some(0));
let skipped = self.extract_number(&text, "Skipped:").or(Some(0));
// If we couldn't parse counts, use exit code
let (total, passed, failed, skipped) = if total.is_none() || passed.is_none() {
@@ -441,7 +441,6 @@ impl TestExecutor {
.and_then(|line| {
line.split(label)
.nth(1)?
.trim()
.split_whitespace()
.next()?
.parse::<i32>()

View File

@@ -278,7 +278,7 @@ fn json_eq(a: &JsonValue, b: &JsonValue) -> bool {
return false;
}
a.iter()
.all(|(k, v)| b.get(k).map_or(false, |bv| json_eq(v, bv)))
.all(|(k, v)| b.get(k).is_some_and(|bv| json_eq(v, bv)))
}
// Different types (other than number cross-compare) are never equal
_ => false,
@@ -680,10 +680,8 @@ impl NumericValue {
fn as_numeric(v: &JsonValue) -> Option<NumericValue> {
if let Some(i) = v.as_i64() {
Some(NumericValue::Int(i))
} else if let Some(f) = v.as_f64() {
Some(NumericValue::Float(f))
} else {
None
v.as_f64().map(NumericValue::Float)
}
}

View File

@@ -363,8 +363,8 @@ mod tests {
let ctx = TestContext::new();
assert_eq!(eval_expression("string(42)", &ctx).unwrap(), json!("42"));
assert_eq!(
eval_expression("number(\"3.14\")", &ctx).unwrap(),
json!(3.14)
eval_expression("number(\"3.15\")", &ctx).unwrap(),
json!(3.15)
);
assert_eq!(eval_expression("int(3.9)", &ctx).unwrap(), json!(3));
assert_eq!(eval_expression("int(\"42\")", &ctx).unwrap(), json!(42));

View File

@@ -431,8 +431,8 @@ mod tests {
#[test]
fn test_float() {
let kinds = tokenize("3.14");
assert_eq!(kinds, vec![TokenKind::Float(3.14), TokenKind::Eof]);
let kinds = tokenize("3.15");
assert_eq!(kinds, vec![TokenKind::Float(3.15), TokenKind::Eof]);
}
#[test]

View File

@@ -187,11 +187,13 @@ impl WorkflowLoader {
.map(|e| e.to_string())
};
if validation_error.is_some() && !self.config.skip_validation {
return Err(Error::validation(format!(
"Workflow validation failed: {}",
validation_error.as_ref().unwrap()
)));
if let Some(ref err) = validation_error {
if !self.config.skip_validation {
return Err(Error::validation(format!(
"Workflow validation failed: {}",
err
)));
}
}
Ok(LoadedWorkflow {

View File

@@ -1449,7 +1449,7 @@ tasks:
publish:
- validation_passed: true
- count: 42
- ratio: 3.14
- ratio: 3.15
- label: "hello"
- template_val: "{{ result().data }}"
- nothing: null
@@ -1486,7 +1486,7 @@ tasks:
} else if let Some(val) = map.get("count") {
assert_eq!(val, &serde_json::json!(42), "integer");
} else if let Some(val) = map.get("ratio") {
assert_eq!(val, &serde_json::json!(3.14), "float");
assert_eq!(val, &serde_json::json!(3.15), "float");
} else if let Some(val) = map.get("label") {
assert_eq!(val, &serde_json::json!("hello"), "string");
} else if let Some(val) = map.get("template_val") {

View File

@@ -97,11 +97,11 @@ impl WorkflowRegistrar {
debug!("Registering workflow: {}", loaded.file.ref_name);
// Check for validation errors
if loaded.validation_error.is_some() {
if let Some(ref err) = loaded.validation_error {
if self.options.skip_invalid {
return Err(Error::validation(format!(
"Workflow has validation errors: {}",
loaded.validation_error.as_ref().unwrap()
err
)));
}
}
@@ -252,6 +252,7 @@ impl WorkflowRegistrar {
///
/// `effective_ref` and `effective_label` are the resolved values (which may
/// have been derived from the filename when the workflow YAML omits them).
#[allow(clippy::too_many_arguments)]
async fn create_companion_action(
&self,
workflow_def_id: i64,
@@ -298,6 +299,7 @@ impl WorkflowRegistrar {
///
/// `effective_ref` and `effective_label` are the resolved values (which may
/// have been derived from the filename when the workflow YAML omits them).
#[allow(clippy::too_many_arguments)]
async fn ensure_companion_action(
&self,
workflow_def_id: i64,
@@ -425,8 +427,8 @@ mod tests {
#[test]
fn test_registration_options_default() {
let options = RegistrationOptions::default();
assert_eq!(options.update_existing, true);
assert_eq!(options.skip_invalid, true);
assert!(options.update_existing);
assert!(options.skip_invalid);
}
#[test]
@@ -439,7 +441,7 @@ mod tests {
};
assert_eq!(result.ref_name, "test.workflow");
assert_eq!(result.created, true);
assert!(result.created);
assert_eq!(result.workflow_def_id, 123);
assert_eq!(result.warnings.len(), 0);
}

View File

@@ -308,7 +308,7 @@ impl WorkflowValidator {
reachable
}
/// Detect cycles using DFS
// Detect cycles using DFS
// Cycle detection removed - cycles are now valid in workflow graphs
// Workflows are directed graphs (not DAGs) and cycles are supported
// for use cases like monitoring loops, retry patterns, etc.
@@ -328,7 +328,7 @@ impl WorkflowValidator {
}
// Validate variable names in vars
for (key, _) in &workflow.vars {
for key in workflow.vars.keys() {
if !Self::is_valid_variable_name(key) {
return Err(ValidationError::SemanticError(format!(
"Invalid variable name: {}",

View File

@@ -781,7 +781,7 @@ async fn test_find_executions_by_enforcement() {
config: None,
env_vars: None,
parent: None,
enforcement: if i == 2 { None } else { None }, // Can't reference non-existent enforcement
enforcement: None, // Can't reference non-existent enforcement
executor: None,
status: ExecutionStatus::Requested,
result: None,

View File

@@ -35,7 +35,7 @@ async fn test_create_key_system_owner() {
assert_eq!(key.owner_pack, None);
assert_eq!(key.owner_action, None);
assert_eq!(key.owner_sensor, None);
assert_eq!(key.encrypted, false);
assert!(!key.encrypted);
assert_eq!(key.value, "test_value");
assert!(key.created.timestamp() > 0);
assert!(key.updated.timestamp() > 0);
@@ -52,7 +52,7 @@ async fn test_create_key_system_encrypted() {
.await
.unwrap();
assert_eq!(key.encrypted, true);
assert!(key.encrypted);
assert_eq!(key.encryption_key_hash, Some("sha256:abc123".to_string()));
}
@@ -427,7 +427,7 @@ async fn test_update_encrypted_status() {
.await
.unwrap();
assert_eq!(key.encrypted, false);
assert!(!key.encrypted);
let input = UpdateKeyInput {
encrypted: Some(true),
@@ -438,7 +438,7 @@ async fn test_update_encrypted_status() {
let updated = KeyRepository::update(&pool, key.id, input).await.unwrap();
assert_eq!(updated.encrypted, true);
assert!(updated.encrypted);
assert_eq!(
updated.encryption_key_hash,
Some("sha256:xyz789".to_string())
@@ -468,7 +468,7 @@ async fn test_update_multiple_fields() {
assert_eq!(updated.name, new_name);
assert_eq!(updated.value, "updated_value");
assert_eq!(updated.encrypted, true);
assert!(updated.encrypted);
assert_eq!(updated.encryption_key_hash, Some("hash123".to_string()));
}
@@ -768,10 +768,10 @@ async fn test_key_encrypted_flag() {
.await
.unwrap();
assert_eq!(plain_key.encrypted, false);
assert!(!plain_key.encrypted);
assert_eq!(plain_key.encryption_key_hash, None);
assert_eq!(encrypted_key.encrypted, true);
assert!(encrypted_key.encrypted);
assert_eq!(
encrypted_key.encryption_key_hash,
Some("sha256:abc".to_string())
@@ -788,7 +788,7 @@ async fn test_update_encryption_status() {
.await
.unwrap();
assert_eq!(key.encrypted, false);
assert!(!key.encrypted);
// Encrypt it
let input = UpdateKeyInput {
@@ -800,7 +800,7 @@ async fn test_update_encryption_status() {
let encrypted = KeyRepository::update(&pool, key.id, input).await.unwrap();
assert_eq!(encrypted.encrypted, true);
assert!(encrypted.encrypted);
assert_eq!(
encrypted.encryption_key_hash,
Some("sha256:newkey".to_string())
@@ -817,7 +817,7 @@ async fn test_update_encryption_status() {
let decrypted = KeyRepository::update(&pool, key.id, input).await.unwrap();
assert_eq!(decrypted.encrypted, false);
assert!(!decrypted.encrypted);
assert_eq!(decrypted.value, "plain_value");
}

View File

@@ -892,7 +892,7 @@ async fn test_port_range() {
let worker = WorkerRepository::create(&pool, input)
.await
.expect(&format!("Failed to create worker with port {}", port));
.unwrap_or_else(|_| panic!("Failed to create worker with port {}", port));
assert_eq!(worker.port, Some(port));
}

View File

@@ -74,7 +74,7 @@ async fn test_create_rule() {
rule.conditions,
json!({"equals": {"event.status": "success"}})
);
assert_eq!(rule.enabled, true);
assert!(rule.enabled);
assert!(rule.created.timestamp() > 0);
assert!(rule.updated.timestamp() > 0);
}
@@ -117,7 +117,7 @@ async fn test_create_rule_disabled() {
let rule = RuleRepository::create(&pool, input).await.unwrap();
assert_eq!(rule.enabled, false);
assert!(!rule.enabled);
}
#[tokio::test]
@@ -759,7 +759,7 @@ async fn test_update_rule_enabled() {
.await
.unwrap();
assert_eq!(updated.enabled, false);
assert!(!updated.enabled);
}
#[tokio::test]
@@ -816,7 +816,7 @@ async fn test_update_rule_multiple_fields() {
assert_eq!(updated.label, "New Label");
assert_eq!(updated.description, "New Description");
assert_eq!(updated.conditions, json!({"updated": true}));
assert_eq!(updated.enabled, false);
assert!(!updated.enabled);
}
#[tokio::test]

View File

@@ -61,7 +61,7 @@ async fn test_create_sensor_minimal() {
assert_eq!(sensor.runtime_ref, runtime.r#ref);
assert_eq!(sensor.trigger, trigger.id);
assert_eq!(sensor.trigger_ref, trigger.r#ref);
assert_eq!(sensor.enabled, true);
assert!(sensor.enabled);
assert_eq!(sensor.param_schema, None);
assert!(sensor.created.timestamp() > 0);
assert!(sensor.updated.timestamp() > 0);
@@ -796,7 +796,7 @@ async fn test_update_enabled_status() {
.await
.unwrap();
assert_eq!(sensor.enabled, true);
assert!(sensor.enabled);
let input = UpdateSensorInput {
enabled: Some(false),
@@ -807,7 +807,7 @@ async fn test_update_enabled_status() {
.await
.unwrap();
assert_eq!(updated.enabled, false);
assert!(!updated.enabled);
// Enable it again
let input = UpdateSensorInput {
@@ -819,7 +819,7 @@ async fn test_update_enabled_status() {
.await
.unwrap();
assert_eq!(updated.enabled, true);
assert!(updated.enabled);
}
#[tokio::test]
@@ -924,7 +924,7 @@ async fn test_update_multiple_fields() {
assert_eq!(updated.label, "Multi Update");
assert_eq!(updated.description, "Updated multiple fields");
assert_eq!(updated.entrypoint, "sensors/multi.py");
assert_eq!(updated.enabled, false);
assert!(!updated.enabled);
assert_eq!(updated.param_schema, Some(json!({"type": "object"})));
}

View File

@@ -42,7 +42,7 @@ async fn test_create_trigger() {
assert_eq!(trigger.pack, Some(pack.id));
assert_eq!(trigger.pack_ref, Some(pack.r#ref));
assert_eq!(trigger.label, "Webhook Trigger");
assert_eq!(trigger.enabled, true);
assert!(trigger.enabled);
assert!(trigger.created.timestamp() > 0);
assert!(trigger.updated.timestamp() > 0);
}
@@ -134,7 +134,7 @@ async fn test_create_trigger_disabled() {
let trigger = TriggerRepository::create(&pool, input).await.unwrap();
assert_eq!(trigger.enabled, false);
assert!(!trigger.enabled);
}
#[tokio::test]
@@ -478,7 +478,7 @@ async fn test_update_trigger() {
assert_eq!(updated.r#ref, trigger.r#ref); // Ref should not change
assert_eq!(updated.label, "Updated Label");
assert_eq!(updated.description, Some("Updated description".to_string()));
assert_eq!(updated.enabled, false);
assert!(!updated.enabled);
assert!(updated.updated > original_updated);
}

View File

@@ -546,7 +546,7 @@ mod tests {
let manager = TimerManager::new(api_client).await.unwrap();
// Test various valid cron expressions
let expressions = vec![
let expressions = [
"0 0 * * * *", // Every hour
"0 */15 * * * *", // Every 15 minutes
"0 0 0 * * *", // Daily at midnight

View File

@@ -150,6 +150,7 @@ impl TimerConfig {
/// Rule lifecycle event types
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "event_type", rename_all = "PascalCase")]
#[allow(clippy::enum_variant_names)]
pub enum RuleLifecycleEvent {
RuleCreated {
rule_id: i64,

View File

@@ -116,13 +116,7 @@ impl CompletionListener {
// Verify execution exists in database
let execution = ExecutionRepository::find_by_id(pool, execution_id).await?;
if execution.is_none() {
warn!(
"Execution {} not found in database, but still releasing queue slot",
execution_id
);
} else {
let exec = execution.as_ref().unwrap();
if let Some(ref exec) = execution {
debug!(
"Execution {} found with status: {:?}",
execution_id, exec.status
@@ -180,6 +174,11 @@ impl CompletionListener {
}
}
}
} else {
warn!(
"Execution {} not found in database, but still releasing queue slot",
execution_id
);
}
// Release queue slot for this action

View File

@@ -77,8 +77,7 @@ impl DeadLetterHandler {
info!("Dead letter handler stopping, rejecting message");
return Err(attune_common::mq::MqError::Consume(
"Handler is shutting down".to_string(),
)
.into());
));
}
}

View File

@@ -189,7 +189,7 @@ impl EventProcessor {
let payload_dict = payload
.as_object()
.cloned()
.unwrap_or_else(|| serde_json::Map::new());
.unwrap_or_else(serde_json::Map::new);
// Resolve action parameters using the template resolver
let resolved_params = Self::resolve_action_params(pool, rule, event, &payload).await?;
@@ -248,7 +248,7 @@ impl EventProcessor {
};
// If rule has no conditions, it always matches
if rule.conditions.is_null() || rule.conditions.as_array().map_or(true, |a| a.is_empty()) {
if rule.conditions.is_null() || rule.conditions.as_array().is_none_or(|a| a.is_empty()) {
debug!("Rule {} has no conditions, matching by default", rule.r#ref);
return Ok(true);
}
@@ -364,7 +364,7 @@ impl EventProcessor {
let action_params = &rule.action_params;
// If there are no action params, return empty
if action_params.is_null() || action_params.as_object().map_or(true, |o| o.is_empty()) {
if action_params.is_null() || action_params.as_object().is_none_or(|o| o.is_empty()) {
return Ok(serde_json::Map::new());
}

View File

@@ -257,7 +257,6 @@ mod tests {
fn test_execution_manager_creation() {
// This is a placeholder test
// Real tests will require database and message queue setup
assert!(true);
}
#[test]

View File

@@ -21,6 +21,7 @@ use crate::queue_manager::ExecutionQueueManager;
/// Policy violation type
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[allow(clippy::enum_variant_names)]
pub enum PolicyViolation {
/// Rate limit exceeded
RateLimitExceeded {
@@ -78,7 +79,7 @@ impl std::fmt::Display for PolicyViolation {
}
/// Execution policy configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ExecutionPolicy {
/// Rate limit: maximum executions per time window
pub rate_limit: Option<RateLimit>,
@@ -88,16 +89,6 @@ pub struct ExecutionPolicy {
pub quotas: Option<HashMap<String, u64>>,
}
impl Default for ExecutionPolicy {
fn default() -> Self {
Self {
rate_limit: None,
concurrency_limit: None,
quotas: None,
}
}
}
/// Rate limit configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RateLimit {

View File

@@ -78,11 +78,7 @@ fn apply_param_defaults(params: JsonValue, param_schema: &Option<JsonValue>) ->
if let Some(schema_obj) = schema.as_object() {
for (key, prop) in schema_obj {
// Only fill in missing / null parameters
let needs_default = match obj.get(key) {
None => true,
Some(JsonValue::Null) => true,
_ => false,
};
let needs_default = matches!(obj.get(key), None | Some(JsonValue::Null));
if needs_default {
if let Some(default_val) = prop.get("default") {
debug!("Applying default for parameter '{}': {}", key, default_val);
@@ -411,6 +407,7 @@ impl ExecutionScheduler {
/// `triggered_by` is the name of the predecessor task whose completion
/// caused this task to be scheduled. Pass `None` for entry-point tasks
/// dispatched at workflow start.
#[allow(clippy::too_many_arguments)]
async fn dispatch_workflow_task(
pool: &PgPool,
publisher: &Publisher,
@@ -496,10 +493,8 @@ impl ExecutionScheduler {
let task_config: Option<JsonValue> =
if rendered_input.is_object() && !rendered_input.as_object().unwrap().is_empty() {
Some(rendered_input.clone())
} else if let Some(parent_config) = &parent_execution.config {
Some(parent_config.clone())
} else {
None
parent_execution.config.clone()
};
// Build workflow task metadata
@@ -660,10 +655,8 @@ impl ExecutionScheduler {
let task_config: Option<JsonValue> =
if rendered_input.is_object() && !rendered_input.as_object().unwrap().is_empty() {
Some(rendered_input.clone())
} else if let Some(parent_config) = &parent_execution.config {
Some(parent_config.clone())
} else {
None
parent_execution.config.clone()
};
let workflow_task = WorkflowTaskMetadata {
@@ -1108,16 +1101,16 @@ impl ExecutionScheduler {
let mut task_results_map: HashMap<String, JsonValue> = HashMap::new();
for child in &child_executions {
if let Some(ref wt) = child.workflow_task {
if wt.workflow_execution == workflow_execution_id {
if matches!(
if wt.workflow_execution == workflow_execution_id
&& matches!(
child.status,
ExecutionStatus::Completed
| ExecutionStatus::Failed
| ExecutionStatus::Timeout
) {
let result_val = child.result.clone().unwrap_or(serde_json::json!({}));
task_results_map.insert(wt.task_name.clone(), result_val);
}
)
{
let result_val = child.result.clone().unwrap_or(serde_json::json!({}));
task_results_map.insert(wt.task_name.clone(), result_val);
}
}
}
@@ -1514,7 +1507,7 @@ impl ExecutionScheduler {
// Filter by heartbeat freshness (only workers with recent heartbeats)
let fresh_workers: Vec<_> = active_workers
.into_iter()
.filter(|w| Self::is_worker_heartbeat_fresh(w))
.filter(Self::is_worker_heartbeat_fresh)
.collect();
if fresh_workers.is_empty() {
@@ -1749,27 +1742,23 @@ mod tests {
fn test_scheduler_creation() {
// This is a placeholder test
// Real tests will require database and message queue setup
assert!(true);
}
#[test]
fn test_concurrency_limit_dispatch_count() {
// Verify the dispatch_count calculation used by dispatch_with_items_task
let total = 20usize;
let concurrency: Option<usize> = Some(3);
let concurrency_limit = concurrency.unwrap_or(total);
let concurrency_limit = 3usize;
let dispatch_count = total.min(concurrency_limit);
assert_eq!(dispatch_count, 3);
// No concurrency limit → default to serial (1 at a time)
let concurrency: Option<usize> = None;
let concurrency_limit = concurrency.unwrap_or(1);
let concurrency_limit = 1usize;
let dispatch_count = total.min(concurrency_limit);
assert_eq!(dispatch_count, 1);
// Concurrency exceeds total → dispatch all
let concurrency: Option<usize> = Some(50);
let concurrency_limit = concurrency.unwrap_or(total);
let concurrency_limit = 50usize;
let dispatch_count = total.min(concurrency_limit);
assert_eq!(dispatch_count, 20);
}

View File

@@ -406,7 +406,7 @@ impl WorkerHealthProbe {
runtime_array.iter().any(|v| {
v.as_str()
.map_or(false, |s| s.eq_ignore_ascii_case(runtime_name))
.is_some_and(|s| s.eq_ignore_ascii_case(runtime_name))
})
}
}

View File

@@ -1129,7 +1129,7 @@ mod tests {
let mut publish_map = HashMap::new();
publish_map.insert("flag".to_string(), JsonValue::Bool(true));
publish_map.insert("count".to_string(), json!(42));
publish_map.insert("ratio".to_string(), json!(3.14));
publish_map.insert("ratio".to_string(), json!(3.15));
publish_map.insert("nothing".to_string(), JsonValue::Null);
publish_map.insert(
"template".to_string(),
@@ -1152,7 +1152,7 @@ mod tests {
assert!(ctx.get_var("count").unwrap().is_number());
// Float preserved
assert_eq!(ctx.get_var("ratio").unwrap(), json!(3.14));
assert_eq!(ctx.get_var("ratio").unwrap(), json!(3.15));
// Null preserved
assert_eq!(ctx.get_var("nothing").unwrap(), json!(null));

View File

@@ -965,7 +965,7 @@ tasks:
publish:
- validation_passed: true
- count: 42
- ratio: 3.14
- ratio: 3.15
- label: "hello"
- template_val: "{{ result().data }}"
- nothing: null
@@ -1003,7 +1003,7 @@ tasks:
assert_eq!(publish_map["count"], &serde_json::json!(42));
// Float is preserved as a JSON number
assert_eq!(publish_map["ratio"], &serde_json::json!(3.14));
assert_eq!(publish_map["ratio"], &serde_json::json!(3.15));
// Plain string stays as a string
assert_eq!(

View File

@@ -357,7 +357,7 @@ impl SensorManager {
let mut child = cmd
.env("ATTUNE_API_URL", &self.inner.api_url)
.env("ATTUNE_API_TOKEN", &token_response.token)
.env("ATTUNE_SENSOR_ID", &sensor.id.to_string())
.env("ATTUNE_SENSOR_ID", sensor.id.to_string())
.env("ATTUNE_SENSOR_REF", &sensor.r#ref)
.env("ATTUNE_SENSOR_TRIGGERS", &trigger_instances_json)
.env("ATTUNE_MQ_URL", &self.inner.mq_url)
@@ -731,6 +731,7 @@ impl SensorInstance {
/// Sensor status information
#[derive(Clone, Debug)]
#[derive(Default)]
pub struct SensorStatus {
/// Whether the sensor is running
pub running: bool,
@@ -745,16 +746,6 @@ pub struct SensorStatus {
pub last_poll: Option<chrono::DateTime<chrono::Utc>>,
}
impl Default for SensorStatus {
fn default() -> Self {
Self {
running: false,
failed: false,
failure_count: 0,
last_poll: None,
}
}
}
#[cfg(test)]
mod tests {

View File

@@ -381,14 +381,12 @@ async fn setup_environments_for_pack(
)
.await;
// Also set up version-specific environments
let versions = match RuntimeVersionRepository::find_available_by_runtime(
db_pool, runtime_id,
)
.await
{
Ok(v) => v,
Err(_) => Vec::new(),
};
let versions: Vec<attune_common::models::RuntimeVersion> =
RuntimeVersionRepository::find_available_by_runtime(
db_pool, runtime_id,
)
.await
.unwrap_or_default();
setup_version_environments_from_list(
&versions,
&rt_name,

View File

@@ -63,6 +63,7 @@ fn normalize_api_url(raw_url: &str) -> String {
impl ActionExecutor {
/// Create a new action executor
#[allow(clippy::too_many_arguments)]
pub fn new(
pool: PgPool,
runtime_registry: RuntimeRegistry,
@@ -359,18 +360,16 @@ impl ActionExecutor {
// Add context data as environment variables from config
if let Some(config) = &execution.config {
if let Some(context) = config.get("context") {
if let JsonValue::Object(map) = context {
for (key, value) in map {
let env_key = format!("ATTUNE_CONTEXT_{}", key.to_uppercase());
let env_value = match value {
JsonValue::String(s) => s.clone(),
JsonValue::Number(n) => n.to_string(),
JsonValue::Bool(b) => b.to_string(),
_ => serde_json::to_string(value)?,
};
env.insert(env_key, env_value);
}
if let Some(JsonValue::Object(map)) = config.get("context") {
for (key, value) in map {
let env_key = format!("ATTUNE_CONTEXT_{}", key.to_uppercase());
let env_value = match value {
JsonValue::String(s) => s.clone(),
JsonValue::Number(n) => n.to_string(),
JsonValue::Bool(b) => b.to_string(),
_ => serde_json::to_string(value)?,
};
env.insert(env_key, env_value);
}
}
}

View File

@@ -45,7 +45,7 @@ impl WorkerRegistration {
let worker_type = config
.worker
.as_ref()
.and_then(|w| w.worker_type.clone())
.and_then(|w| w.worker_type)
.unwrap_or(WorkerType::Local);
let worker_role = WorkerRole::Action;
@@ -180,8 +180,8 @@ impl WorkerRegistration {
"#,
)
.bind(&self.worker_name)
.bind(&self.worker_type)
.bind(&self.worker_role)
.bind(self.worker_type)
.bind(self.worker_role)
.bind(self.runtime_id)
.bind(&self.host)
.bind(self.port)

View File

@@ -35,6 +35,7 @@ impl NativeRuntime {
}
/// Execute a native binary with parameters and environment variables
#[allow(clippy::too_many_arguments)]
async fn execute_binary(
&self,
binary_path: PathBuf,

View File

@@ -117,7 +117,7 @@ pub fn create_parameter_file(
) -> Result<NamedTempFile, RuntimeError> {
let formatted = format_parameters(parameters, format)?;
let mut temp_file = NamedTempFile::new().map_err(|e| RuntimeError::IoError(e))?;
let mut temp_file = NamedTempFile::new().map_err(RuntimeError::IoError)?;
// Set restrictive permissions (owner read-only)
#[cfg(unix)]
@@ -126,20 +126,20 @@ pub fn create_parameter_file(
let mut perms = temp_file
.as_file()
.metadata()
.map_err(|e| RuntimeError::IoError(e))?
.map_err(RuntimeError::IoError)?
.permissions();
perms.set_mode(0o400); // Read-only for owner
temp_file
.as_file()
.set_permissions(perms)
.map_err(|e| RuntimeError::IoError(e))?;
.map_err(RuntimeError::IoError)?;
}
temp_file
.write_all(formatted.as_bytes())
.map_err(|e| RuntimeError::IoError(e))?;
.map_err(RuntimeError::IoError)?;
temp_file.flush().map_err(|e| RuntimeError::IoError(e))?;
temp_file.flush().map_err(RuntimeError::IoError)?;
debug!(
"Created parameter file at {:?} with format {:?}",

View File

@@ -83,6 +83,7 @@ pub async fn execute_streaming(
/// * `max_stderr_bytes` - Maximum stderr size before truncation
/// * `output_format` - How to parse stdout (Text, Json, Yaml, Jsonl)
/// * `cancel_token` - Optional cancellation token for graceful process termination
#[allow(clippy::too_many_arguments)]
pub async fn execute_streaming_cancellable(
mut cmd: Command,
secrets: &HashMap<String, String>,

View File

@@ -61,6 +61,7 @@ impl ShellRuntime {
}
/// Execute with streaming and bounded log collection
#[allow(clippy::too_many_arguments)]
async fn execute_with_streaming(
&self,
mut cmd: Command,
@@ -383,6 +384,7 @@ impl ShellRuntime {
}
/// Execute shell script from file
#[allow(clippy::too_many_arguments)]
async fn execute_shell_file(
&self,
script_path: PathBuf,

View File

@@ -220,7 +220,7 @@ impl SecretManager {
.map_err(|e| Error::Internal(format!("Encryption failed: {}", e)))?;
// Format: "nonce:ciphertext" (both base64-encoded)
let nonce_b64 = BASE64.encode(&nonce);
let nonce_b64 = BASE64.encode(nonce);
let ciphertext_b64 = BASE64.encode(&ciphertext);
Ok(format!("{}:{}", nonce_b64, ciphertext_b64))

View File

@@ -443,6 +443,7 @@ impl WorkerService {
/// 3. Wait for in-flight tasks with timeout
/// 4. Close MQ connection
/// 5. Close DB connection
///
/// Verify which runtime versions are available on this host/container.
///
/// Runs each version's verification commands (from `distributions` JSONB)
@@ -634,7 +635,7 @@ impl WorkerService {
shutdown_timeout
);
let timeout_duration = Duration::from_secs(shutdown_timeout as u64);
let timeout_duration = Duration::from_secs(shutdown_timeout);
match tokio::time::timeout(timeout_duration, self.wait_for_in_flight_tasks()).await {
Ok(_) => info!("All in-flight tasks completed"),
Err(_) => warn!("Shutdown timeout reached - some tasks may have been interrupted"),

View File

@@ -90,7 +90,7 @@ pub async fn verify_all_runtime_versions(
let rt_base_name = version
.runtime_ref
.split('.')
.last()
.next_back()
.unwrap_or(&version.runtime_ref)
.to_lowercase();

View File

@@ -1,6 +1,6 @@
{
"$schema": "https://unpkg.com/knip@latest/schema.json",
"entry": ["src/main.tsx", "vite.config.ts"],
"entry": ["src/**/*.{ts,tsx}", "vite.config.ts"],
"project": ["src/**/*.{ts,tsx}", "scripts/**/*.js"],
"ignore": ["src/api/**", "dist/**", "node_modules/**"]
}

272
web/package-lock.json generated
View File

@@ -15,8 +15,7 @@
"lucide-react": "^0.562.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^7.12.0",
"zustand": "^5.0.10"
"react-router-dom": "^7.12.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
@@ -1100,9 +1099,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.0.tgz",
"integrity": "sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
"integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
"cpu": [
"arm"
],
@@ -1114,9 +1113,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.0.tgz",
"integrity": "sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
"integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
"cpu": [
"arm64"
],
@@ -1128,9 +1127,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.0.tgz",
"integrity": "sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
"integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
"cpu": [
"arm64"
],
@@ -1142,9 +1141,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.0.tgz",
"integrity": "sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
"integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
"cpu": [
"x64"
],
@@ -1156,9 +1155,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.0.tgz",
"integrity": "sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
"integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
"cpu": [
"arm64"
],
@@ -1170,9 +1169,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.0.tgz",
"integrity": "sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
"integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
"cpu": [
"x64"
],
@@ -1184,9 +1183,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.0.tgz",
"integrity": "sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
"integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
"cpu": [
"arm"
],
@@ -1198,9 +1197,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.0.tgz",
"integrity": "sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
"integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
"cpu": [
"arm"
],
@@ -1212,9 +1211,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.0.tgz",
"integrity": "sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
"integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
"cpu": [
"arm64"
],
@@ -1226,9 +1225,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.0.tgz",
"integrity": "sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
"integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
"cpu": [
"arm64"
],
@@ -1240,9 +1239,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.0.tgz",
"integrity": "sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
"integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
"cpu": [
"loong64"
],
@@ -1254,9 +1253,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.0.tgz",
"integrity": "sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
"integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
"cpu": [
"loong64"
],
@@ -1268,9 +1267,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.0.tgz",
"integrity": "sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
"integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
"cpu": [
"ppc64"
],
@@ -1282,9 +1281,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.0.tgz",
"integrity": "sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
"integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
"cpu": [
"ppc64"
],
@@ -1296,9 +1295,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.0.tgz",
"integrity": "sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
"integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
"cpu": [
"riscv64"
],
@@ -1310,9 +1309,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.0.tgz",
"integrity": "sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
"integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
"cpu": [
"riscv64"
],
@@ -1324,9 +1323,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.0.tgz",
"integrity": "sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
"integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
"cpu": [
"s390x"
],
@@ -1338,9 +1337,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.0.tgz",
"integrity": "sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
"integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
"cpu": [
"x64"
],
@@ -1352,9 +1351,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.0.tgz",
"integrity": "sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
"integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
"cpu": [
"x64"
],
@@ -1366,9 +1365,9 @@
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.0.tgz",
"integrity": "sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
"integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
"cpu": [
"x64"
],
@@ -1380,9 +1379,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.0.tgz",
"integrity": "sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
"integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
"cpu": [
"arm64"
],
@@ -1394,9 +1393,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.0.tgz",
"integrity": "sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
"integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
"cpu": [
"arm64"
],
@@ -1408,9 +1407,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.0.tgz",
"integrity": "sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
"integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
"cpu": [
"ia32"
],
@@ -1422,9 +1421,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.0.tgz",
"integrity": "sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
"integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
"cpu": [
"x64"
],
@@ -1436,9 +1435,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.0.tgz",
"integrity": "sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
"integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
"cpu": [
"x64"
],
@@ -1557,7 +1556,7 @@
"version": "19.2.10",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz",
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
"devOptional": true,
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
@@ -1774,13 +1773,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
@@ -1890,9 +1889,9 @@
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2000,13 +1999,13 @@
}
},
"node_modules/axios": {
"version": "1.13.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz",
"integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==",
"version": "1.13.6",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"follow-redirects": "^1.15.11",
"form-data": "^4.0.5",
"proxy-from-env": "^1.1.0"
}
},
@@ -2322,7 +2321,7 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"devOptional": true,
"dev": true,
"license": "MIT"
},
"node_modules/date-fns": {
@@ -3430,9 +3429,9 @@
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -4036,9 +4035,9 @@
}
},
"node_modules/rollup": {
"version": "4.57.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.0.tgz",
"integrity": "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==",
"version": "4.59.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
"integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -4052,31 +4051,31 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.57.0",
"@rollup/rollup-android-arm64": "4.57.0",
"@rollup/rollup-darwin-arm64": "4.57.0",
"@rollup/rollup-darwin-x64": "4.57.0",
"@rollup/rollup-freebsd-arm64": "4.57.0",
"@rollup/rollup-freebsd-x64": "4.57.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.57.0",
"@rollup/rollup-linux-arm-musleabihf": "4.57.0",
"@rollup/rollup-linux-arm64-gnu": "4.57.0",
"@rollup/rollup-linux-arm64-musl": "4.57.0",
"@rollup/rollup-linux-loong64-gnu": "4.57.0",
"@rollup/rollup-linux-loong64-musl": "4.57.0",
"@rollup/rollup-linux-ppc64-gnu": "4.57.0",
"@rollup/rollup-linux-ppc64-musl": "4.57.0",
"@rollup/rollup-linux-riscv64-gnu": "4.57.0",
"@rollup/rollup-linux-riscv64-musl": "4.57.0",
"@rollup/rollup-linux-s390x-gnu": "4.57.0",
"@rollup/rollup-linux-x64-gnu": "4.57.0",
"@rollup/rollup-linux-x64-musl": "4.57.0",
"@rollup/rollup-openbsd-x64": "4.57.0",
"@rollup/rollup-openharmony-arm64": "4.57.0",
"@rollup/rollup-win32-arm64-msvc": "4.57.0",
"@rollup/rollup-win32-ia32-msvc": "4.57.0",
"@rollup/rollup-win32-x64-gnu": "4.57.0",
"@rollup/rollup-win32-x64-msvc": "4.57.0",
"@rollup/rollup-android-arm-eabi": "4.59.0",
"@rollup/rollup-android-arm64": "4.59.0",
"@rollup/rollup-darwin-arm64": "4.59.0",
"@rollup/rollup-darwin-x64": "4.59.0",
"@rollup/rollup-freebsd-arm64": "4.59.0",
"@rollup/rollup-freebsd-x64": "4.59.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
"@rollup/rollup-linux-arm-musleabihf": "4.59.0",
"@rollup/rollup-linux-arm64-gnu": "4.59.0",
"@rollup/rollup-linux-arm64-musl": "4.59.0",
"@rollup/rollup-linux-loong64-gnu": "4.59.0",
"@rollup/rollup-linux-loong64-musl": "4.59.0",
"@rollup/rollup-linux-ppc64-gnu": "4.59.0",
"@rollup/rollup-linux-ppc64-musl": "4.59.0",
"@rollup/rollup-linux-riscv64-gnu": "4.59.0",
"@rollup/rollup-linux-riscv64-musl": "4.59.0",
"@rollup/rollup-linux-s390x-gnu": "4.59.0",
"@rollup/rollup-linux-x64-gnu": "4.59.0",
"@rollup/rollup-linux-x64-musl": "4.59.0",
"@rollup/rollup-openbsd-x64": "4.59.0",
"@rollup/rollup-openharmony-arm64": "4.59.0",
"@rollup/rollup-win32-arm64-msvc": "4.59.0",
"@rollup/rollup-win32-ia32-msvc": "4.59.0",
"@rollup/rollup-win32-x64-gnu": "4.59.0",
"@rollup/rollup-win32-x64-msvc": "4.59.0",
"fsevents": "~2.3.2"
}
},
@@ -4699,35 +4698,6 @@
"peerDependencies": {
"zod": "^3.25.0 || ^4.0.0"
}
},
"node_modules/zustand": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.10.tgz",
"integrity": "sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
}
}
}

View File

@@ -20,8 +20,7 @@
"lucide-react": "^0.562.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-router-dom": "^7.12.0",
"zustand": "^5.0.10"
"react-router-dom": "^7.12.0"
},
"devDependencies": {
"@eslint/js": "^9.39.1",

View File

@@ -3,7 +3,7 @@ import { createRoot } from "react-dom/client";
import "./index.css";
import "./lib/api-config";
import { initializeApiWrapper } from "./lib/api-wrapper";
import App from "./App.tsx";
import App from "./App";
// Initialize API wrapper for token refresh
initializeApiWrapper();