Add multi-database data layer

Introduce aether-data-schema and driver-specific schema generation for Postgres, MySQL, and SQLite.

Split data backends, lifecycle, repositories, and gateway runtime integration across database drivers.

Verified with cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, and cargo test --workspace.
This commit is contained in:
fawney19
2026-05-05 18:27:36 +08:00
parent 099653f732
commit fce7e959e5
372 changed files with 86217 additions and 21160 deletions
@@ -4,14 +4,13 @@ use std::time::Duration;
use aether_gateway::tunnel_protocol as protocol;
use aether_gateway::GatewayDataConfig;
use aether_testkit::{
init_test_runtime_for, reserve_local_port, run_http_load_probe, wait_until, GatewayHarness,
GatewayHarnessConfig, HttpLoadProbeConfig, HttpLoadProbeResponseMode, HttpLoadProbeResult,
ManagedPostgresServer, ManagedRedisServer,
init_test_runtime_for, prepare_aether_postgres_schema, reserve_local_port, run_http_load_probe,
wait_until, GatewayHarness, GatewayHarnessConfig, HttpLoadProbeConfig,
HttpLoadProbeResponseMode, HttpLoadProbeResult, ManagedPostgresServer, ManagedRedisServer,
};
use futures_util::{SinkExt, StreamExt};
use reqwest::Method;
use serde::Serialize;
use sqlx::{Connection, Executor, PgConnection};
use tokio_tungstenite::tungstenite::client::IntoClientRequest;
use tokio_tungstenite::tungstenite::Message;
@@ -113,7 +112,7 @@ async fn run_suite(
})
.expect("postgres url should be resolved");
ensure_owner_relay_schema(&postgres_url).await?;
prepare_aether_postgres_schema(&postgres_url).await?;
let key_prefix = format!("aether-owner-relay-baseline-{}", std::process::id());
let shared_data = GatewayDataConfig::from_postgres_url(postgres_url.clone(), false)
@@ -206,89 +205,6 @@ async fn run_suite(
})
}
async fn ensure_owner_relay_schema(postgres_url: &str) -> Result<(), Box<dyn std::error::Error>> {
let mut connection = PgConnection::connect(postgres_url).await?;
connection
.execute(
r#"
DO $$
BEGIN
IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'proxynodestatus') THEN
CREATE TYPE proxynodestatus AS ENUM ('online', 'offline');
END IF;
END
$$;
"#,
)
.await?;
connection
.execute(
r#"
CREATE TABLE IF NOT EXISTS system_configs (
id VARCHAR(36) PRIMARY KEY,
key VARCHAR(100) UNIQUE NOT NULL,
value JSON NOT NULL,
description TEXT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
)
"#,
)
.await?;
connection
.execute(
r#"
CREATE TABLE IF NOT EXISTS proxy_nodes (
id VARCHAR(36) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
ip VARCHAR(512) NOT NULL,
port INTEGER NOT NULL,
region VARCHAR(100) NULL,
is_manual BOOLEAN NOT NULL DEFAULT FALSE,
proxy_url VARCHAR(500) NULL,
proxy_username VARCHAR(255) NULL,
proxy_password VARCHAR(500) NULL,
status proxynodestatus NOT NULL DEFAULT 'online',
registered_by VARCHAR(36) NULL,
last_heartbeat_at TIMESTAMPTZ NULL,
heartbeat_interval INTEGER NOT NULL DEFAULT 30,
active_connections INTEGER NOT NULL DEFAULT 0,
total_requests BIGINT NOT NULL DEFAULT 0,
avg_latency_ms DOUBLE PRECISION NULL,
failed_requests BIGINT NOT NULL DEFAULT 0,
dns_failures BIGINT NOT NULL DEFAULT 0,
stream_errors BIGINT NOT NULL DEFAULT 0,
proxy_metadata JSON NULL,
hardware_info JSON NULL,
estimated_max_concurrency INTEGER NULL,
tunnel_mode BOOLEAN NOT NULL DEFAULT FALSE,
tunnel_connected BOOLEAN NOT NULL DEFAULT FALSE,
tunnel_connected_at TIMESTAMPTZ NULL,
remote_config JSON NULL,
config_version INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
)
"#,
)
.await?;
connection
.execute(
r#"
CREATE TABLE IF NOT EXISTS proxy_node_events (
id BIGSERIAL PRIMARY KEY,
node_id VARCHAR(36) NOT NULL,
event_type TEXT NOT NULL,
detail TEXT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
)
"#,
)
.await?;
connection.close().await?;
Ok(())
}
async fn wait_for_owner_attachment(forwarder_base_url: &str) -> Result<(), String> {
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(2))