mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 09:20:22 +08:00
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:
File diff suppressed because it is too large
Load Diff
@@ -1,10 +1,15 @@
|
||||
mod memory;
|
||||
mod sql;
|
||||
mod mysql;
|
||||
mod postgres;
|
||||
mod sqlite;
|
||||
mod types;
|
||||
|
||||
pub use memory::InMemoryUserReadRepository;
|
||||
pub use sql::SqlxUserReadRepository;
|
||||
pub use mysql::MysqlUserReadRepository;
|
||||
pub use postgres::SqlxUserReadRepository;
|
||||
pub use sqlite::SqliteUserReadRepository;
|
||||
pub use types::{
|
||||
StoredUserAuthRecord, StoredUserExportRow, StoredUserPreferenceRecord, StoredUserSessionRecord,
|
||||
StoredUserSummary, UserExportListQuery, UserExportSummary, UserReadRepository,
|
||||
StoredUserAuthRecord, StoredUserExportRow, StoredUserOAuthLinkSummary,
|
||||
StoredUserPreferenceRecord, StoredUserSessionRecord, StoredUserSummary, UserExportListQuery,
|
||||
UserExportSummary, UserReadRepository,
|
||||
};
|
||||
|
||||
1465
crates/aether-data/src/repository/users/mysql.rs
Normal file
1465
crates/aether-data/src/repository/users/mysql.rs
Normal file
File diff suppressed because it is too large
Load Diff
2067
crates/aether-data/src/repository/users/postgres.rs
Normal file
2067
crates/aether-data/src/repository/users/postgres.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,473 +0,0 @@
|
||||
use async_trait::async_trait;
|
||||
use futures_util::TryStreamExt;
|
||||
use sqlx::{PgPool, Postgres, QueryBuilder, Row};
|
||||
|
||||
use super::types::{
|
||||
StoredUserAuthRecord, StoredUserExportRow, StoredUserSummary, UserExportListQuery,
|
||||
UserExportSummary, UserReadRepository,
|
||||
};
|
||||
use crate::{error::SqlxResultExt, DataLayerError};
|
||||
|
||||
const LIST_USERS_BY_IDS_SQL: &str = r#"
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
email,
|
||||
role::text AS role,
|
||||
is_active,
|
||||
is_deleted
|
||||
FROM users
|
||||
WHERE id = ANY($1::text[])
|
||||
ORDER BY id ASC
|
||||
"#;
|
||||
|
||||
const LIST_USERS_BY_USERNAME_SEARCH_SQL: &str = r#"
|
||||
SELECT
|
||||
id,
|
||||
username,
|
||||
email,
|
||||
role::text AS role,
|
||||
is_active,
|
||||
is_deleted
|
||||
FROM users
|
||||
WHERE is_deleted IS FALSE
|
||||
AND LOWER(username) LIKE $1
|
||||
ORDER BY id ASC
|
||||
"#;
|
||||
|
||||
const LIST_NON_ADMIN_EXPORT_USERS_SQL: &str = r#"
|
||||
SELECT
|
||||
id,
|
||||
email,
|
||||
email_verified,
|
||||
username,
|
||||
password_hash,
|
||||
role::text AS role,
|
||||
auth_source::text AS auth_source,
|
||||
allowed_providers,
|
||||
allowed_api_formats,
|
||||
allowed_models,
|
||||
rate_limit,
|
||||
model_capability_settings,
|
||||
is_active
|
||||
FROM users
|
||||
WHERE is_deleted IS FALSE
|
||||
AND role::text != 'admin'
|
||||
ORDER BY id ASC
|
||||
"#;
|
||||
|
||||
const LIST_EXPORT_USERS_SQL: &str = r#"
|
||||
SELECT
|
||||
id,
|
||||
email,
|
||||
email_verified,
|
||||
username,
|
||||
password_hash,
|
||||
role::text AS role,
|
||||
auth_source::text AS auth_source,
|
||||
allowed_providers,
|
||||
allowed_api_formats,
|
||||
allowed_models,
|
||||
rate_limit,
|
||||
model_capability_settings,
|
||||
is_active
|
||||
FROM users
|
||||
WHERE is_deleted IS FALSE
|
||||
ORDER BY id ASC
|
||||
"#;
|
||||
|
||||
const LIST_EXPORT_USERS_PAGE_PREFIX: &str = r#"
|
||||
SELECT
|
||||
id,
|
||||
email,
|
||||
email_verified,
|
||||
username,
|
||||
password_hash,
|
||||
role::text AS role,
|
||||
auth_source::text AS auth_source,
|
||||
allowed_providers,
|
||||
allowed_api_formats,
|
||||
allowed_models,
|
||||
rate_limit,
|
||||
model_capability_settings,
|
||||
is_active
|
||||
FROM users
|
||||
WHERE is_deleted IS FALSE
|
||||
"#;
|
||||
|
||||
const SUMMARIZE_EXPORT_USERS_SQL: &str = r#"
|
||||
SELECT
|
||||
COUNT(*)::BIGINT AS total,
|
||||
COUNT(*) FILTER (WHERE is_active = TRUE)::BIGINT AS active
|
||||
FROM users
|
||||
WHERE is_deleted IS FALSE
|
||||
"#;
|
||||
|
||||
const FIND_EXPORT_USER_BY_ID_SQL: &str = r#"
|
||||
SELECT
|
||||
id,
|
||||
email,
|
||||
email_verified,
|
||||
username,
|
||||
password_hash,
|
||||
role::text AS role,
|
||||
auth_source::text AS auth_source,
|
||||
allowed_providers,
|
||||
allowed_api_formats,
|
||||
allowed_models,
|
||||
rate_limit,
|
||||
model_capability_settings,
|
||||
is_active
|
||||
FROM users
|
||||
WHERE is_deleted IS FALSE
|
||||
AND id = $1
|
||||
LIMIT 1
|
||||
"#;
|
||||
|
||||
const FIND_USER_AUTH_BY_ID_SQL: &str = r#"
|
||||
SELECT
|
||||
id,
|
||||
email,
|
||||
email_verified,
|
||||
username,
|
||||
password_hash,
|
||||
role::text AS role,
|
||||
auth_source::text AS auth_source,
|
||||
allowed_providers,
|
||||
allowed_api_formats,
|
||||
allowed_models,
|
||||
is_active,
|
||||
is_deleted,
|
||||
created_at,
|
||||
last_login_at
|
||||
FROM users
|
||||
WHERE id = $1
|
||||
LIMIT 1
|
||||
"#;
|
||||
|
||||
const LIST_USER_AUTH_BY_IDS_SQL: &str = r#"
|
||||
SELECT
|
||||
id,
|
||||
email,
|
||||
email_verified,
|
||||
username,
|
||||
password_hash,
|
||||
role::text AS role,
|
||||
auth_source::text AS auth_source,
|
||||
allowed_providers,
|
||||
allowed_api_formats,
|
||||
allowed_models,
|
||||
is_active,
|
||||
is_deleted,
|
||||
created_at,
|
||||
last_login_at
|
||||
FROM users
|
||||
WHERE id = ANY($1::text[])
|
||||
ORDER BY id ASC
|
||||
"#;
|
||||
|
||||
const FIND_USER_AUTH_BY_IDENTIFIER_SQL: &str = r#"
|
||||
SELECT
|
||||
id,
|
||||
email,
|
||||
email_verified,
|
||||
username,
|
||||
password_hash,
|
||||
role::text AS role,
|
||||
auth_source::text AS auth_source,
|
||||
allowed_providers,
|
||||
allowed_api_formats,
|
||||
allowed_models,
|
||||
is_active,
|
||||
is_deleted,
|
||||
created_at,
|
||||
last_login_at
|
||||
FROM users
|
||||
WHERE email = $1 OR username = $1
|
||||
LIMIT 1
|
||||
"#;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SqlxUserReadRepository {
|
||||
pool: PgPool,
|
||||
}
|
||||
|
||||
impl SqlxUserReadRepository {
|
||||
pub fn new(pool: PgPool) -> Self {
|
||||
Self { pool }
|
||||
}
|
||||
|
||||
pub async fn list_users_by_ids(
|
||||
&self,
|
||||
user_ids: &[String],
|
||||
) -> Result<Vec<StoredUserSummary>, DataLayerError> {
|
||||
if user_ids.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
collect_query_rows(
|
||||
sqlx::query(LIST_USERS_BY_IDS_SQL)
|
||||
.bind(user_ids)
|
||||
.fetch(&self.pool),
|
||||
map_user_row,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn list_users_by_username_search(
|
||||
&self,
|
||||
username_search: &str,
|
||||
) -> Result<Vec<StoredUserSummary>, DataLayerError> {
|
||||
let username_search = username_search.trim();
|
||||
if username_search.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
collect_query_rows(
|
||||
sqlx::query(LIST_USERS_BY_USERNAME_SEARCH_SQL)
|
||||
.bind(format!("%{}%", username_search.to_ascii_lowercase()))
|
||||
.fetch(&self.pool),
|
||||
map_user_row,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn list_non_admin_export_users(
|
||||
&self,
|
||||
) -> Result<Vec<StoredUserExportRow>, DataLayerError> {
|
||||
collect_query_rows(
|
||||
sqlx::query(LIST_NON_ADMIN_EXPORT_USERS_SQL).fetch(&self.pool),
|
||||
map_user_export_row,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn list_export_users(&self) -> Result<Vec<StoredUserExportRow>, DataLayerError> {
|
||||
collect_query_rows(
|
||||
sqlx::query(LIST_EXPORT_USERS_SQL).fetch(&self.pool),
|
||||
map_user_export_row,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn list_export_users_page(
|
||||
&self,
|
||||
query: &UserExportListQuery,
|
||||
) -> Result<Vec<StoredUserExportRow>, DataLayerError> {
|
||||
let mut builder = QueryBuilder::<Postgres>::new(LIST_EXPORT_USERS_PAGE_PREFIX);
|
||||
|
||||
if let Some(role) = query.role.as_deref() {
|
||||
builder
|
||||
.push(" AND LOWER(role::text) = ")
|
||||
.push_bind(role.trim().to_ascii_lowercase());
|
||||
}
|
||||
if let Some(is_active) = query.is_active {
|
||||
builder.push(" AND is_active = ").push_bind(is_active);
|
||||
}
|
||||
|
||||
builder
|
||||
.push(" ORDER BY id ASC OFFSET ")
|
||||
.push_bind(i64::try_from(query.skip).map_err(|_| {
|
||||
DataLayerError::InvalidInput(format!("invalid user export skip: {}", query.skip))
|
||||
})?)
|
||||
.push(" LIMIT ")
|
||||
.push_bind(i64::try_from(query.limit).map_err(|_| {
|
||||
DataLayerError::InvalidInput(format!("invalid user export limit: {}", query.limit))
|
||||
})?);
|
||||
|
||||
let query = builder.build();
|
||||
collect_query_rows(query.fetch(&self.pool), map_user_export_row).await
|
||||
}
|
||||
|
||||
pub async fn summarize_export_users(&self) -> Result<UserExportSummary, DataLayerError> {
|
||||
let row = sqlx::query(SUMMARIZE_EXPORT_USERS_SQL)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_postgres_err()?;
|
||||
Ok(UserExportSummary {
|
||||
total: row.try_get::<i64, _>("total").map_postgres_err()?.max(0) as u64,
|
||||
active: row.try_get::<i64, _>("active").map_postgres_err()?.max(0) as u64,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn find_export_user_by_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> Result<Option<StoredUserExportRow>, DataLayerError> {
|
||||
let row = sqlx::query(FIND_EXPORT_USER_BY_ID_SQL)
|
||||
.bind(user_id)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_postgres_err()?;
|
||||
row.as_ref().map(map_user_export_row).transpose()
|
||||
}
|
||||
|
||||
pub async fn list_user_auth_by_ids(
|
||||
&self,
|
||||
user_ids: &[String],
|
||||
) -> Result<Vec<StoredUserAuthRecord>, DataLayerError> {
|
||||
if user_ids.is_empty() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
||||
collect_query_rows(
|
||||
sqlx::query(LIST_USER_AUTH_BY_IDS_SQL)
|
||||
.bind(user_ids)
|
||||
.fetch(&self.pool),
|
||||
map_user_auth_row,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_user_auth_by_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
|
||||
let row = sqlx::query(FIND_USER_AUTH_BY_ID_SQL)
|
||||
.bind(user_id)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_postgres_err()?;
|
||||
row.as_ref().map(map_user_auth_row).transpose()
|
||||
}
|
||||
|
||||
pub async fn find_user_auth_by_identifier(
|
||||
&self,
|
||||
identifier: &str,
|
||||
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
|
||||
let row = sqlx::query(FIND_USER_AUTH_BY_IDENTIFIER_SQL)
|
||||
.bind(identifier)
|
||||
.fetch_optional(&self.pool)
|
||||
.await
|
||||
.map_postgres_err()?;
|
||||
row.as_ref().map(map_user_auth_row).transpose()
|
||||
}
|
||||
}
|
||||
|
||||
fn map_user_row(row: &sqlx::postgres::PgRow) -> Result<StoredUserSummary, DataLayerError> {
|
||||
StoredUserSummary::new(
|
||||
row.try_get("id").map_postgres_err()?,
|
||||
row.try_get("username").map_postgres_err()?,
|
||||
row.try_get("email").map_postgres_err()?,
|
||||
row.try_get("role").map_postgres_err()?,
|
||||
row.try_get("is_active").map_postgres_err()?,
|
||||
row.try_get("is_deleted").map_postgres_err()?,
|
||||
)
|
||||
}
|
||||
|
||||
fn map_user_export_row(row: &sqlx::postgres::PgRow) -> Result<StoredUserExportRow, DataLayerError> {
|
||||
StoredUserExportRow::new(
|
||||
row.try_get("id").map_postgres_err()?,
|
||||
row.try_get("email").map_postgres_err()?,
|
||||
row.try_get("email_verified").map_postgres_err()?,
|
||||
row.try_get("username").map_postgres_err()?,
|
||||
row.try_get("password_hash").map_postgres_err()?,
|
||||
row.try_get("role").map_postgres_err()?,
|
||||
row.try_get("auth_source").map_postgres_err()?,
|
||||
row.try_get("allowed_providers").map_postgres_err()?,
|
||||
row.try_get("allowed_api_formats").map_postgres_err()?,
|
||||
row.try_get("allowed_models").map_postgres_err()?,
|
||||
row.try_get("rate_limit").map_postgres_err()?,
|
||||
row.try_get("model_capability_settings")
|
||||
.map_postgres_err()?,
|
||||
row.try_get("is_active").map_postgres_err()?,
|
||||
)
|
||||
}
|
||||
|
||||
fn map_user_auth_row(row: &sqlx::postgres::PgRow) -> Result<StoredUserAuthRecord, DataLayerError> {
|
||||
StoredUserAuthRecord::new(
|
||||
row.try_get("id").map_postgres_err()?,
|
||||
row.try_get("email").map_postgres_err()?,
|
||||
row.try_get("email_verified").map_postgres_err()?,
|
||||
row.try_get("username").map_postgres_err()?,
|
||||
row.try_get("password_hash").map_postgres_err()?,
|
||||
row.try_get("role").map_postgres_err()?,
|
||||
row.try_get("auth_source").map_postgres_err()?,
|
||||
row.try_get("allowed_providers").map_postgres_err()?,
|
||||
row.try_get("allowed_api_formats").map_postgres_err()?,
|
||||
row.try_get("allowed_models").map_postgres_err()?,
|
||||
row.try_get("is_active").map_postgres_err()?,
|
||||
row.try_get("is_deleted").map_postgres_err()?,
|
||||
row.try_get("created_at").map_postgres_err()?,
|
||||
row.try_get("last_login_at").map_postgres_err()?,
|
||||
)
|
||||
}
|
||||
|
||||
async fn collect_query_rows<T, S>(
|
||||
mut rows: S,
|
||||
mapper: fn(&sqlx::postgres::PgRow) -> Result<T, DataLayerError>,
|
||||
) -> Result<Vec<T>, DataLayerError>
|
||||
where
|
||||
S: futures_util::TryStream<Ok = sqlx::postgres::PgRow, Error = sqlx::Error> + Unpin,
|
||||
{
|
||||
let mut items = Vec::new();
|
||||
while let Some(row) = rows.try_next().await.map_postgres_err()? {
|
||||
items.push(mapper(&row)?);
|
||||
}
|
||||
Ok(items)
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl UserReadRepository for SqlxUserReadRepository {
|
||||
async fn list_users_by_ids(
|
||||
&self,
|
||||
user_ids: &[String],
|
||||
) -> Result<Vec<StoredUserSummary>, DataLayerError> {
|
||||
self.list_users_by_ids(user_ids).await
|
||||
}
|
||||
|
||||
async fn list_users_by_username_search(
|
||||
&self,
|
||||
username_search: &str,
|
||||
) -> Result<Vec<StoredUserSummary>, DataLayerError> {
|
||||
self.list_users_by_username_search(username_search).await
|
||||
}
|
||||
|
||||
async fn list_non_admin_export_users(
|
||||
&self,
|
||||
) -> Result<Vec<StoredUserExportRow>, DataLayerError> {
|
||||
self.list_non_admin_export_users().await
|
||||
}
|
||||
|
||||
async fn list_export_users(&self) -> Result<Vec<StoredUserExportRow>, DataLayerError> {
|
||||
self.list_export_users().await
|
||||
}
|
||||
|
||||
async fn list_export_users_page(
|
||||
&self,
|
||||
query: &UserExportListQuery,
|
||||
) -> Result<Vec<StoredUserExportRow>, DataLayerError> {
|
||||
self.list_export_users_page(query).await
|
||||
}
|
||||
|
||||
async fn summarize_export_users(&self) -> Result<UserExportSummary, DataLayerError> {
|
||||
self.summarize_export_users().await
|
||||
}
|
||||
|
||||
async fn find_export_user_by_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> Result<Option<StoredUserExportRow>, DataLayerError> {
|
||||
self.find_export_user_by_id(user_id).await
|
||||
}
|
||||
|
||||
async fn find_user_auth_by_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
|
||||
self.find_user_auth_by_id(user_id).await
|
||||
}
|
||||
|
||||
async fn list_user_auth_by_ids(
|
||||
&self,
|
||||
user_ids: &[String],
|
||||
) -> Result<Vec<StoredUserAuthRecord>, DataLayerError> {
|
||||
self.list_user_auth_by_ids(user_ids).await
|
||||
}
|
||||
|
||||
async fn find_user_auth_by_identifier(
|
||||
&self,
|
||||
identifier: &str,
|
||||
) -> Result<Option<StoredUserAuthRecord>, DataLayerError> {
|
||||
self.find_user_auth_by_identifier(identifier).await
|
||||
}
|
||||
}
|
||||
1912
crates/aether-data/src/repository/users/sqlite.rs
Normal file
1912
crates/aether-data/src/repository/users/sqlite.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -137,6 +137,56 @@ impl StoredUserAuthRecord {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct LdapAuthUserProvisioningOutcome {
|
||||
pub user: StoredUserAuthRecord,
|
||||
pub created: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct StoredUserOAuthLinkSummary {
|
||||
pub provider_type: String,
|
||||
pub display_name: String,
|
||||
pub provider_username: Option<String>,
|
||||
pub provider_email: Option<String>,
|
||||
pub linked_at: Option<DateTime<Utc>>,
|
||||
pub last_login_at: Option<DateTime<Utc>>,
|
||||
pub provider_enabled: bool,
|
||||
}
|
||||
|
||||
impl StoredUserOAuthLinkSummary {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
provider_type: String,
|
||||
display_name: String,
|
||||
provider_username: Option<String>,
|
||||
provider_email: Option<String>,
|
||||
linked_at: Option<DateTime<Utc>>,
|
||||
last_login_at: Option<DateTime<Utc>>,
|
||||
provider_enabled: bool,
|
||||
) -> Result<Self, crate::DataLayerError> {
|
||||
if provider_type.trim().is_empty() {
|
||||
return Err(crate::DataLayerError::UnexpectedValue(
|
||||
"user_oauth_links.provider_type is empty".to_string(),
|
||||
));
|
||||
}
|
||||
if display_name.trim().is_empty() {
|
||||
return Err(crate::DataLayerError::UnexpectedValue(
|
||||
"oauth_providers.display_name is empty".to_string(),
|
||||
));
|
||||
}
|
||||
Ok(Self {
|
||||
provider_type,
|
||||
display_name,
|
||||
provider_username,
|
||||
provider_email,
|
||||
linked_at,
|
||||
last_login_at,
|
||||
provider_enabled,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
|
||||
pub struct StoredUserExportRow {
|
||||
pub id: String,
|
||||
@@ -430,6 +480,232 @@ pub trait UserReadRepository: Send + Sync {
|
||||
&self,
|
||||
identifier: &str,
|
||||
) -> Result<Option<StoredUserAuthRecord>, crate::DataLayerError>;
|
||||
|
||||
async fn find_user_auth_by_email(
|
||||
&self,
|
||||
email: &str,
|
||||
) -> Result<Option<StoredUserAuthRecord>, crate::DataLayerError>;
|
||||
|
||||
async fn find_active_user_auth_by_email_ci(
|
||||
&self,
|
||||
email: &str,
|
||||
) -> Result<Option<StoredUserAuthRecord>, crate::DataLayerError>;
|
||||
|
||||
async fn find_user_auth_by_username(
|
||||
&self,
|
||||
username: &str,
|
||||
) -> Result<Option<StoredUserAuthRecord>, crate::DataLayerError>;
|
||||
|
||||
async fn list_user_oauth_links(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> Result<Vec<StoredUserOAuthLinkSummary>, crate::DataLayerError>;
|
||||
|
||||
async fn find_oauth_linked_user(
|
||||
&self,
|
||||
provider_type: &str,
|
||||
provider_user_id: &str,
|
||||
) -> Result<Option<StoredUserAuthRecord>, crate::DataLayerError>;
|
||||
|
||||
async fn touch_oauth_link(
|
||||
&self,
|
||||
provider_type: &str,
|
||||
provider_user_id: &str,
|
||||
provider_username: Option<&str>,
|
||||
provider_email: Option<&str>,
|
||||
extra_data: Option<Value>,
|
||||
touched_at: DateTime<Utc>,
|
||||
) -> Result<bool, crate::DataLayerError>;
|
||||
|
||||
async fn create_oauth_auth_user(
|
||||
&self,
|
||||
email: Option<String>,
|
||||
username: String,
|
||||
created_at: DateTime<Utc>,
|
||||
) -> Result<Option<StoredUserAuthRecord>, crate::DataLayerError>;
|
||||
|
||||
async fn find_oauth_link_owner(
|
||||
&self,
|
||||
provider_type: &str,
|
||||
provider_user_id: &str,
|
||||
) -> Result<Option<String>, crate::DataLayerError>;
|
||||
|
||||
async fn has_user_oauth_provider_link(
|
||||
&self,
|
||||
user_id: &str,
|
||||
provider_type: &str,
|
||||
) -> Result<bool, crate::DataLayerError>;
|
||||
|
||||
async fn count_user_oauth_links(&self, user_id: &str) -> Result<u64, crate::DataLayerError>;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn upsert_user_oauth_link(
|
||||
&self,
|
||||
user_id: &str,
|
||||
provider_type: &str,
|
||||
provider_user_id: &str,
|
||||
provider_username: Option<&str>,
|
||||
provider_email: Option<&str>,
|
||||
extra_data: Option<Value>,
|
||||
linked_at: DateTime<Utc>,
|
||||
) -> Result<(), crate::DataLayerError>;
|
||||
|
||||
async fn delete_user_oauth_link(
|
||||
&self,
|
||||
user_id: &str,
|
||||
provider_type: &str,
|
||||
) -> Result<bool, crate::DataLayerError>;
|
||||
|
||||
async fn get_or_create_ldap_auth_user(
|
||||
&self,
|
||||
email: String,
|
||||
username: String,
|
||||
ldap_dn: Option<String>,
|
||||
ldap_username: Option<String>,
|
||||
logged_in_at: DateTime<Utc>,
|
||||
) -> Result<Option<LdapAuthUserProvisioningOutcome>, crate::DataLayerError>;
|
||||
|
||||
async fn touch_auth_user_last_login(
|
||||
&self,
|
||||
user_id: &str,
|
||||
logged_in_at: DateTime<Utc>,
|
||||
) -> Result<bool, crate::DataLayerError>;
|
||||
|
||||
async fn update_local_auth_user_profile(
|
||||
&self,
|
||||
user_id: &str,
|
||||
email: Option<String>,
|
||||
username: Option<String>,
|
||||
) -> Result<Option<StoredUserAuthRecord>, crate::DataLayerError>;
|
||||
|
||||
async fn update_local_auth_user_password_hash(
|
||||
&self,
|
||||
user_id: &str,
|
||||
password_hash: String,
|
||||
updated_at: DateTime<Utc>,
|
||||
) -> Result<Option<StoredUserAuthRecord>, crate::DataLayerError>;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn update_local_auth_user_admin_fields(
|
||||
&self,
|
||||
user_id: &str,
|
||||
role: Option<String>,
|
||||
allowed_providers_present: bool,
|
||||
allowed_providers: Option<Vec<String>>,
|
||||
allowed_api_formats_present: bool,
|
||||
allowed_api_formats: Option<Vec<String>>,
|
||||
allowed_models_present: bool,
|
||||
allowed_models: Option<Vec<String>>,
|
||||
rate_limit: Option<i32>,
|
||||
is_active: Option<bool>,
|
||||
) -> Result<Option<StoredUserAuthRecord>, crate::DataLayerError>;
|
||||
|
||||
async fn update_user_model_capability_settings(
|
||||
&self,
|
||||
user_id: &str,
|
||||
settings: Option<Value>,
|
||||
) -> Result<Option<Value>, crate::DataLayerError>;
|
||||
|
||||
async fn create_local_auth_user(
|
||||
&self,
|
||||
email: Option<String>,
|
||||
email_verified: bool,
|
||||
username: String,
|
||||
password_hash: String,
|
||||
) -> Result<Option<StoredUserAuthRecord>, crate::DataLayerError>;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn create_local_auth_user_with_settings(
|
||||
&self,
|
||||
email: Option<String>,
|
||||
email_verified: bool,
|
||||
username: String,
|
||||
password_hash: String,
|
||||
role: String,
|
||||
allowed_providers: Option<Vec<String>>,
|
||||
allowed_api_formats: Option<Vec<String>>,
|
||||
allowed_models: Option<Vec<String>>,
|
||||
rate_limit: Option<i32>,
|
||||
) -> Result<Option<StoredUserAuthRecord>, crate::DataLayerError>;
|
||||
|
||||
async fn delete_local_auth_user(&self, user_id: &str) -> Result<bool, crate::DataLayerError>;
|
||||
|
||||
async fn read_user_preferences(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> Result<Option<StoredUserPreferenceRecord>, crate::DataLayerError>;
|
||||
|
||||
async fn write_user_preferences(
|
||||
&self,
|
||||
preferences: &StoredUserPreferenceRecord,
|
||||
) -> Result<Option<StoredUserPreferenceRecord>, crate::DataLayerError>;
|
||||
|
||||
async fn find_user_session(
|
||||
&self,
|
||||
user_id: &str,
|
||||
session_id: &str,
|
||||
) -> Result<Option<StoredUserSessionRecord>, crate::DataLayerError>;
|
||||
|
||||
async fn list_user_sessions(
|
||||
&self,
|
||||
user_id: &str,
|
||||
) -> Result<Vec<StoredUserSessionRecord>, crate::DataLayerError>;
|
||||
|
||||
async fn create_user_session(
|
||||
&self,
|
||||
session: &StoredUserSessionRecord,
|
||||
) -> Result<Option<StoredUserSessionRecord>, crate::DataLayerError>;
|
||||
|
||||
async fn touch_user_session(
|
||||
&self,
|
||||
user_id: &str,
|
||||
session_id: &str,
|
||||
touched_at: DateTime<Utc>,
|
||||
ip_address: Option<&str>,
|
||||
user_agent: Option<&str>,
|
||||
) -> Result<bool, crate::DataLayerError>;
|
||||
|
||||
async fn update_user_session_device_label(
|
||||
&self,
|
||||
user_id: &str,
|
||||
session_id: &str,
|
||||
device_label: &str,
|
||||
updated_at: DateTime<Utc>,
|
||||
) -> Result<bool, crate::DataLayerError>;
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
async fn rotate_user_session_refresh_token(
|
||||
&self,
|
||||
user_id: &str,
|
||||
session_id: &str,
|
||||
previous_refresh_token_hash: &str,
|
||||
next_refresh_token_hash: &str,
|
||||
rotated_at: DateTime<Utc>,
|
||||
expires_at: DateTime<Utc>,
|
||||
ip_address: Option<&str>,
|
||||
user_agent: Option<&str>,
|
||||
) -> Result<bool, crate::DataLayerError>;
|
||||
|
||||
async fn revoke_user_session(
|
||||
&self,
|
||||
user_id: &str,
|
||||
session_id: &str,
|
||||
revoked_at: DateTime<Utc>,
|
||||
reason: &str,
|
||||
) -> Result<bool, crate::DataLayerError>;
|
||||
|
||||
async fn revoke_all_user_sessions(
|
||||
&self,
|
||||
user_id: &str,
|
||||
revoked_at: DateTime<Utc>,
|
||||
reason: &str,
|
||||
) -> Result<u64, crate::DataLayerError>;
|
||||
|
||||
async fn count_active_admin_users(&self) -> Result<u64, crate::DataLayerError>;
|
||||
|
||||
async fn count_active_local_admin_users_with_valid_password(
|
||||
&self,
|
||||
) -> Result<u64, crate::DataLayerError>;
|
||||
}
|
||||
|
||||
fn normalize_optional_json(value: Option<Value>) -> Option<Value> {
|
||||
|
||||
Reference in New Issue
Block a user