Merge remote-tracking branch 'origin/pr/487'

# Conflicts:
#	crates/aether-data/src/lifecycle/bootstrap/postgres.rs
#	crates/aether-data/src/lifecycle/migrate/tests.rs
#	crates/aether-data/src/repository/oauth_providers/postgres.rs
#	crates/aether-data/src/repository/oauth_providers/sqlite.rs
#	frontend/src/views/admin/OAuthSettings.vue
This commit is contained in:
fawney19
2026-05-19 02:27:39 +08:00
27 changed files with 293 additions and 204 deletions

View File

@@ -0,0 +1 @@
ALTER TABLE oauth_providers ADD COLUMN icon_url VARCHAR(500);

View File

@@ -0,0 +1 @@
ALTER TABLE public.oauth_providers ADD COLUMN IF NOT EXISTS icon_url VARCHAR(500);

View File

@@ -0,0 +1 @@
ALTER TABLE oauth_providers ADD COLUMN icon_url TEXT;

View File

@@ -416,6 +416,7 @@ CREATE TABLE IF NOT EXISTS public.oauth_providers (
frontend_callback_url character varying(500) NOT NULL,
attribute_mapping json,
extra_config json,
icon_url character varying(500),
is_enabled boolean DEFAULT false NOT NULL,
created_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL

View File

@@ -36,6 +36,7 @@ CREATE TABLE IF NOT EXISTS oauth_providers (
`frontend_callback_url` VARCHAR(500) NOT NULL,
`attribute_mapping` JSON,
`extra_config` JSON,
`icon_url` VARCHAR(500),
`is_enabled` TINYINT(1) NOT NULL DEFAULT 0,
`created_at` BIGINT NOT NULL,
`updated_at` BIGINT NOT NULL,

View File

@@ -38,6 +38,7 @@ CREATE TABLE IF NOT EXISTS public.oauth_providers (
frontend_callback_url character varying(500) NOT NULL,
attribute_mapping jsonb,
extra_config jsonb,
icon_url character varying(500),
is_enabled boolean DEFAULT false NOT NULL,
created_at bigint NOT NULL,
updated_at bigint NOT NULL

View File

@@ -34,6 +34,7 @@ CREATE TABLE IF NOT EXISTS oauth_providers (
frontend_callback_url TEXT NOT NULL,
attribute_mapping TEXT,
extra_config TEXT,
icon_url TEXT,
is_enabled INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL

View File

@@ -137,6 +137,12 @@ name = "extra_config"
type = "json"
nullable = true
[[table.oauth_providers.columns]]
name = "icon_url"
type = "text"
length = 500
nullable = true
[[table.oauth_providers.columns]]
name = "is_enabled"
type = "bool"

View File

@@ -7,7 +7,7 @@ use tracing::info;
// Generated by build.rs from schema/bootstrap/postgres.
pub(crate) static EMPTY_DATABASE_SNAPSHOT_SQL: &str =
include_str!(concat!(env!("OUT_DIR"), "/empty_database_snapshot.sql"));
pub(crate) const EMPTY_DATABASE_SNAPSHOT_CUTOFF_VERSION: i64 = 20260519000000;
pub(crate) const EMPTY_DATABASE_SNAPSHOT_CUTOFF_VERSION: i64 = 20260519120000;
const PUBLIC_BASE_TABLE_COUNT_SQL: &str = r#"
SELECT COUNT(*)::BIGINT

View File

@@ -308,6 +308,7 @@ fn empty_database_snapshot_covers_current_cutoff_versions() {
20260516000000,
20260518000000,
20260519000000,
20260519120000,
]
);
}
@@ -598,7 +599,9 @@ fn mysql_and_sqlite_migrations_include_enabled_incrementals() {
20260512090000,
20260512110000,
20260516000000,
20260518000000,
20260519000000,
20260519120000,
]
);
assert_eq!(
@@ -616,7 +619,9 @@ fn mysql_and_sqlite_migrations_include_enabled_incrementals() {
20260512090000,
20260512110000,
20260516000000,
20260518000000,
20260519000000,
20260519120000,
]
);
}
@@ -1136,6 +1141,7 @@ fn pending_migrations_from_applied_skips_versions_already_applied() {
20260516000000,
20260518000000,
20260519000000,
20260519120000,
]
);
}

View File

@@ -100,6 +100,7 @@ impl OAuthProviderWriteRepository for InMemoryOAuthProviderRepository {
record.scopes.clone(),
record.attribute_mapping.clone(),
record.extra_config.clone(),
record.icon_url.clone(),
record.is_enabled,
)
.with_timestamps(created_at, now);
@@ -150,6 +151,7 @@ mod tests {
frontend_callback_url: "https://frontend.example.com/auth/callback".to_string(),
attribute_mapping: Some(serde_json::json!({"email": "email"})),
extra_config: Some(serde_json::json!({"team": true})),
icon_url: None,
is_enabled: true,
}
}

View File

@@ -46,6 +46,7 @@ SELECT
frontend_callback_url,
attribute_mapping,
extra_config,
icon_url,
is_enabled,
created_at AS created_at_unix_ms,
updated_at AS updated_at_unix_secs
@@ -67,6 +68,7 @@ SELECT
frontend_callback_url,
attribute_mapping,
extra_config,
icon_url,
is_enabled,
created_at AS created_at_unix_ms,
updated_at AS updated_at_unix_secs
@@ -176,13 +178,14 @@ INSERT INTO oauth_providers (
frontend_callback_url,
attribute_mapping,
extra_config,
icon_url,
is_enabled,
created_at,
updated_at
) VALUES (
?, ?, ?,
CASE ? WHEN 'set' THEN ? WHEN 'clear' THEN NULL ELSE NULL END,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
ON DUPLICATE KEY UPDATE
display_name = VALUES(display_name),
@@ -200,6 +203,7 @@ ON DUPLICATE KEY UPDATE
frontend_callback_url = VALUES(frontend_callback_url),
attribute_mapping = VALUES(attribute_mapping),
extra_config = VALUES(extra_config),
icon_url = VALUES(icon_url),
is_enabled = VALUES(is_enabled),
updated_at = VALUES(updated_at)
"#,
@@ -217,6 +221,7 @@ ON DUPLICATE KEY UPDATE
.bind(&record.frontend_callback_url)
.bind(json_to_string(record.attribute_mapping.as_ref())?)
.bind(json_to_string(record.extra_config.as_ref())?)
.bind(record.icon_url.as_deref())
.bind(record.is_enabled)
.bind(now as i64)
.bind(now as i64)
@@ -361,6 +366,7 @@ fn map_oauth_provider_row(row: &MySqlRow) -> Result<StoredOAuthProviderConfig, D
row.try_get("extra_config").map_sql_err()?,
"oauth_providers.extra_config",
)?,
row.try_get("icon_url").map_sql_err()?,
row.try_get("is_enabled").map_sql_err()?,
)
.with_timestamps(

View File

@@ -22,6 +22,7 @@ SELECT
frontend_callback_url,
attribute_mapping,
extra_config,
icon_url,
is_enabled,
EXTRACT(EPOCH FROM created_at)::bigint AS created_at_unix_ms,
EXTRACT(EPOCH FROM updated_at)::bigint AS updated_at_unix_secs
@@ -77,6 +78,7 @@ INSERT INTO oauth_providers (
frontend_callback_url,
attribute_mapping,
extra_config,
icon_url,
is_enabled,
created_at,
updated_at
@@ -99,6 +101,7 @@ VALUES (
$12,
$13,
$14,
$15,
NOW(),
NOW()
)
@@ -118,6 +121,7 @@ SET display_name = EXCLUDED.display_name,
frontend_callback_url = EXCLUDED.frontend_callback_url,
attribute_mapping = EXCLUDED.attribute_mapping,
extra_config = EXCLUDED.extra_config,
icon_url = EXCLUDED.icon_url,
is_enabled = EXCLUDED.is_enabled,
updated_at = NOW()
RETURNING
@@ -133,6 +137,7 @@ RETURNING
frontend_callback_url,
attribute_mapping,
extra_config,
icon_url,
is_enabled,
EXTRACT(EPOCH FROM created_at)::bigint AS created_at_unix_ms,
EXTRACT(EPOCH FROM updated_at)::bigint AS updated_at_unix_secs
@@ -230,6 +235,7 @@ impl OAuthProviderWriteRepository for SqlxOAuthProviderRepository {
.bind(&record.frontend_callback_url)
.bind(record.attribute_mapping.as_ref())
.bind(record.extra_config.as_ref())
.bind(record.icon_url.as_deref())
.bind(record.is_enabled)
.fetch_one(&self.pool)
.await
@@ -330,6 +336,7 @@ fn map_oauth_provider_row(row: &PgRow) -> Result<StoredOAuthProviderConfig, Data
parse_scopes(row.try_get("scopes").map_postgres_err()?)?,
row.try_get("attribute_mapping").map_postgres_err()?,
row.try_get("extra_config").map_postgres_err()?,
row.try_get("icon_url").map_postgres_err()?,
row.try_get("is_enabled").map_postgres_err()?,
)
.with_timestamps(

View File

@@ -56,6 +56,7 @@ SELECT
frontend_callback_url,
attribute_mapping,
extra_config,
icon_url,
is_enabled,
created_at AS created_at_unix_ms,
updated_at AS updated_at_unix_secs
@@ -162,13 +163,14 @@ INSERT INTO oauth_providers (
frontend_callback_url,
attribute_mapping,
extra_config,
icon_url,
is_enabled,
created_at,
updated_at
) VALUES (
?, ?, ?,
CASE ? WHEN 'set' THEN ? WHEN 'clear' THEN NULL ELSE NULL END,
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
ON CONFLICT(provider_type) DO UPDATE SET
display_name = excluded.display_name,
@@ -186,6 +188,7 @@ ON CONFLICT(provider_type) DO UPDATE SET
frontend_callback_url = excluded.frontend_callback_url,
attribute_mapping = excluded.attribute_mapping,
extra_config = excluded.extra_config,
icon_url = excluded.icon_url,
is_enabled = excluded.is_enabled,
updated_at = excluded.updated_at
"#,
@@ -203,6 +206,7 @@ ON CONFLICT(provider_type) DO UPDATE SET
.bind(&record.frontend_callback_url)
.bind(json_to_string(record.attribute_mapping.as_ref())?)
.bind(json_to_string(record.extra_config.as_ref())?)
.bind(record.icon_url.as_deref())
.bind(record.is_enabled)
.bind(now as i64)
.bind(now as i64)
@@ -350,6 +354,7 @@ fn map_oauth_provider_row(row: &SqliteRow) -> Result<StoredOAuthProviderConfig,
row.try_get("extra_config").map_sql_err()?,
"oauth_providers.extra_config",
)?,
row.try_get("icon_url").map_sql_err()?,
row.try_get("is_enabled").map_sql_err()?,
)
.with_timestamps(
@@ -381,6 +386,7 @@ mod tests {
frontend_callback_url: "https://frontend.example.com/auth/callback".to_string(),
attribute_mapping: Some(serde_json::json!({"email": "email"})),
extra_config: Some(serde_json::json!({"team": true})),
icon_url: None,
is_enabled: true,
}
}

View File

@@ -14,6 +14,7 @@ pub struct StoredOAuthProviderConfig {
pub frontend_callback_url: String,
pub attribute_mapping: Option<serde_json::Value>,
pub extra_config: Option<serde_json::Value>,
pub icon_url: Option<String>,
pub is_enabled: bool,
pub created_at_unix_ms: Option<u64>,
pub updated_at_unix_secs: Option<u64>,
@@ -66,6 +67,7 @@ impl StoredOAuthProviderConfig {
frontend_callback_url,
attribute_mapping: None,
extra_config: None,
icon_url: None,
is_enabled: false,
created_at_unix_ms: None,
updated_at_unix_secs: None,
@@ -82,6 +84,7 @@ impl StoredOAuthProviderConfig {
scopes: Option<Vec<String>>,
attribute_mapping: Option<serde_json::Value>,
extra_config: Option<serde_json::Value>,
icon_url: Option<String>,
is_enabled: bool,
) -> Self {
self.client_secret_encrypted = client_secret_encrypted;
@@ -91,6 +94,7 @@ impl StoredOAuthProviderConfig {
self.scopes = scopes;
self.attribute_mapping = attribute_mapping;
self.extra_config = extra_config;
self.icon_url = icon_url;
self.is_enabled = is_enabled;
self
}
@@ -145,6 +149,7 @@ pub struct UpsertOAuthProviderConfigRecord {
pub frontend_callback_url: String,
pub attribute_mapping: Option<serde_json::Value>,
pub extra_config: Option<serde_json::Value>,
pub icon_url: Option<String>,
pub is_enabled: bool,
}

View File

@@ -336,8 +336,11 @@ SELECT
users.role::text AS role,
users.auth_source::text AS auth_source,
users.allowed_providers,
users.allowed_providers_mode,
users.allowed_api_formats,
users.allowed_api_formats_mode,
users.allowed_models,
users.allowed_models_mode,
users.is_active,
users.is_deleted,
users.created_at,
@@ -353,7 +356,7 @@ const TOUCH_OAUTH_LINK_SQL: &str = r#"
UPDATE user_oauth_links
SET provider_username = COALESCE($3, provider_username),
provider_email = COALESCE($4, provider_email),
extra_data = COALESCE($5, extra_data),
extra_data = COALESCE($5::json, extra_data),
last_login_at = $6
WHERE provider_type = $1
AND provider_user_id = $2