mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
Add admin user export count queries
This commit is contained in:
@@ -360,6 +360,59 @@ fn memory_group_members(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn filter_memory_export_rows(
|
||||
repository: &InMemoryUserReadRepository,
|
||||
query: &UserExportListQuery,
|
||||
) -> Vec<StoredUserExportRow> {
|
||||
let mut rows = repository
|
||||
.export_rows
|
||||
.read()
|
||||
.expect("user repository lock")
|
||||
.clone();
|
||||
if let Some(role) = query.role.as_deref() {
|
||||
rows.retain(|row| row.role.eq_ignore_ascii_case(role));
|
||||
}
|
||||
if let Some(is_active) = query.is_active {
|
||||
rows.retain(|row| row.is_active == is_active);
|
||||
}
|
||||
if let Some(group_id) = query
|
||||
.group_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
let member_ids = repository
|
||||
.group_members
|
||||
.read()
|
||||
.expect("user repository lock")
|
||||
.keys()
|
||||
.filter_map(|(candidate_group_id, user_id)| {
|
||||
(candidate_group_id == group_id).then(|| user_id.clone())
|
||||
})
|
||||
.collect::<std::collections::BTreeSet<_>>();
|
||||
rows.retain(|row| member_ids.contains(&row.id));
|
||||
}
|
||||
if let Some(search) = query
|
||||
.search
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
let search = search.to_ascii_lowercase();
|
||||
rows.retain(|row| {
|
||||
row.id.to_ascii_lowercase().contains(&search)
|
||||
|| row.username.to_ascii_lowercase().contains(&search)
|
||||
|| row
|
||||
.email
|
||||
.as_deref()
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase()
|
||||
.contains(&search)
|
||||
});
|
||||
}
|
||||
rows
|
||||
}
|
||||
|
||||
fn memory_export_row_from_auth_user(
|
||||
repository: &InMemoryUserReadRepository,
|
||||
user: &StoredUserAuthRecord,
|
||||
@@ -477,59 +530,17 @@ impl UserReadRepository for InMemoryUserReadRepository {
|
||||
&self,
|
||||
query: &UserExportListQuery,
|
||||
) -> Result<Vec<StoredUserExportRow>, DataLayerError> {
|
||||
let mut rows = self
|
||||
.export_rows
|
||||
.read()
|
||||
.expect("user repository lock")
|
||||
.clone();
|
||||
if let Some(role) = query.role.as_deref() {
|
||||
rows.retain(|row| row.role.eq_ignore_ascii_case(role));
|
||||
}
|
||||
if let Some(is_active) = query.is_active {
|
||||
rows.retain(|row| row.is_active == is_active);
|
||||
}
|
||||
if let Some(group_id) = query
|
||||
.group_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
let member_ids = self
|
||||
.group_members
|
||||
.read()
|
||||
.expect("user repository lock")
|
||||
.keys()
|
||||
.filter_map(|(candidate_group_id, user_id)| {
|
||||
(candidate_group_id == group_id).then(|| user_id.clone())
|
||||
})
|
||||
.collect::<std::collections::BTreeSet<_>>();
|
||||
rows.retain(|row| member_ids.contains(&row.id));
|
||||
}
|
||||
if let Some(search) = query
|
||||
.search
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
let search = search.to_ascii_lowercase();
|
||||
rows.retain(|row| {
|
||||
row.id.to_ascii_lowercase().contains(&search)
|
||||
|| row.username.to_ascii_lowercase().contains(&search)
|
||||
|| row
|
||||
.email
|
||||
.as_deref()
|
||||
.unwrap_or_default()
|
||||
.to_ascii_lowercase()
|
||||
.contains(&search)
|
||||
});
|
||||
}
|
||||
Ok(rows
|
||||
Ok(filter_memory_export_rows(self, query)
|
||||
.into_iter()
|
||||
.skip(query.skip)
|
||||
.take(query.limit)
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn count_export_users(&self, query: &UserExportListQuery) -> Result<u64, DataLayerError> {
|
||||
Ok(filter_memory_export_rows(self, query).len() as u64)
|
||||
}
|
||||
|
||||
async fn summarize_export_users(&self) -> Result<UserExportSummary, DataLayerError> {
|
||||
let rows = self.export_rows.read().expect("user repository lock");
|
||||
Ok(UserExportSummary {
|
||||
|
||||
@@ -325,6 +325,48 @@ impl UserReadRepository for MysqlUserReadRepository {
|
||||
self.fetch_export_rows(builder).await
|
||||
}
|
||||
|
||||
async fn count_export_users(&self, query: &UserExportListQuery) -> Result<u64, DataLayerError> {
|
||||
let mut builder = QueryBuilder::<MySql>::new("SELECT COUNT(*) AS total FROM users");
|
||||
builder.push(" WHERE is_deleted = 0");
|
||||
if let Some(role) = query.role.as_deref() {
|
||||
builder
|
||||
.push(" AND LOWER(role) = ")
|
||||
.push_bind(role.trim().to_ascii_lowercase());
|
||||
}
|
||||
if let Some(is_active) = query.is_active {
|
||||
builder.push(" AND is_active = ").push_bind(is_active);
|
||||
}
|
||||
if let Some(group_id) = query
|
||||
.group_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
builder.push(" AND id IN (SELECT user_id FROM user_group_members WHERE group_id = ");
|
||||
builder.push_bind(group_id);
|
||||
builder.push(")");
|
||||
}
|
||||
if let Some(search) = query
|
||||
.search
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
let pattern = format!("%{}%", search.to_ascii_lowercase());
|
||||
builder
|
||||
.push(" AND (LOWER(id) LIKE ")
|
||||
.push_bind(pattern.clone())
|
||||
.push(" OR LOWER(username) LIKE ")
|
||||
.push_bind(pattern.clone())
|
||||
.push(" OR LOWER(COALESCE(email, '')) LIKE ")
|
||||
.push_bind(pattern)
|
||||
.push(")");
|
||||
}
|
||||
|
||||
let row = builder.build().fetch_one(&self.pool).await.map_sql_err()?;
|
||||
Ok(row.try_get::<i64, _>("total").map_sql_err()?.max(0) as u64)
|
||||
}
|
||||
|
||||
async fn summarize_export_users(&self) -> Result<UserExportSummary, DataLayerError> {
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
|
||||
@@ -1015,6 +1015,57 @@ WHERE user_group_members.user_id IN (
|
||||
collect_query_rows(query.fetch(&self.pool), map_user_export_row).await
|
||||
}
|
||||
|
||||
pub async fn count_export_users(
|
||||
&self,
|
||||
query: &UserExportListQuery,
|
||||
) -> Result<u64, DataLayerError> {
|
||||
let mut builder =
|
||||
QueryBuilder::<Postgres>::new("SELECT COUNT(*)::BIGINT AS total FROM users");
|
||||
builder.push(" WHERE is_deleted IS FALSE");
|
||||
|
||||
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);
|
||||
}
|
||||
if let Some(group_id) = query
|
||||
.group_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
builder.push(" AND id IN (SELECT user_id FROM user_group_members WHERE group_id = ");
|
||||
builder.push_bind(group_id);
|
||||
builder.push(")");
|
||||
}
|
||||
if let Some(search) = query
|
||||
.search
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
let pattern = format!("%{}%", search.to_ascii_lowercase());
|
||||
builder
|
||||
.push(" AND (LOWER(id) LIKE ")
|
||||
.push_bind(pattern.clone())
|
||||
.push(" OR LOWER(username) LIKE ")
|
||||
.push_bind(pattern.clone())
|
||||
.push(" OR LOWER(COALESCE(email, '')) LIKE ")
|
||||
.push_bind(pattern)
|
||||
.push(")");
|
||||
}
|
||||
|
||||
let row = builder
|
||||
.build()
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_postgres_err()?;
|
||||
Ok(row.try_get::<i64, _>("total").map_postgres_err()?.max(0) as u64)
|
||||
}
|
||||
|
||||
pub async fn summarize_export_users(&self) -> Result<UserExportSummary, DataLayerError> {
|
||||
let row = sqlx::query(SUMMARIZE_EXPORT_USERS_SQL)
|
||||
.fetch_one(&self.pool)
|
||||
@@ -2306,6 +2357,10 @@ impl UserReadRepository for SqlxUserReadRepository {
|
||||
self.list_export_users_page(query).await
|
||||
}
|
||||
|
||||
async fn count_export_users(&self, query: &UserExportListQuery) -> Result<u64, DataLayerError> {
|
||||
self.count_export_users(query).await
|
||||
}
|
||||
|
||||
async fn summarize_export_users(&self) -> Result<UserExportSummary, DataLayerError> {
|
||||
self.summarize_export_users().await
|
||||
}
|
||||
|
||||
@@ -325,6 +325,48 @@ impl UserReadRepository for SqliteUserReadRepository {
|
||||
self.fetch_export_rows(builder).await
|
||||
}
|
||||
|
||||
async fn count_export_users(&self, query: &UserExportListQuery) -> Result<u64, DataLayerError> {
|
||||
let mut builder = QueryBuilder::<Sqlite>::new("SELECT COUNT(*) AS total FROM users");
|
||||
builder.push(" WHERE is_deleted = 0");
|
||||
if let Some(role) = query.role.as_deref() {
|
||||
builder
|
||||
.push(" AND LOWER(role) = ")
|
||||
.push_bind(role.trim().to_ascii_lowercase());
|
||||
}
|
||||
if let Some(is_active) = query.is_active {
|
||||
builder.push(" AND is_active = ").push_bind(is_active);
|
||||
}
|
||||
if let Some(group_id) = query
|
||||
.group_id
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
builder.push(" AND id IN (SELECT user_id FROM user_group_members WHERE group_id = ");
|
||||
builder.push_bind(group_id);
|
||||
builder.push(")");
|
||||
}
|
||||
if let Some(search) = query
|
||||
.search
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
let pattern = format!("%{}%", search.to_ascii_lowercase());
|
||||
builder
|
||||
.push(" AND (LOWER(id) LIKE ")
|
||||
.push_bind(pattern.clone())
|
||||
.push(" OR LOWER(username) LIKE ")
|
||||
.push_bind(pattern.clone())
|
||||
.push(" OR LOWER(COALESCE(email, '')) LIKE ")
|
||||
.push_bind(pattern)
|
||||
.push(")");
|
||||
}
|
||||
|
||||
let row = builder.build().fetch_one(&self.pool).await.map_sql_err()?;
|
||||
Ok(row.try_get::<i64, _>("total").map_sql_err()?.max(0) as u64)
|
||||
}
|
||||
|
||||
async fn summarize_export_users(&self) -> Result<UserExportSummary, DataLayerError> {
|
||||
let row = sqlx::query(
|
||||
r#"
|
||||
|
||||
@@ -669,6 +669,11 @@ pub trait UserReadRepository: Send + Sync {
|
||||
query: &UserExportListQuery,
|
||||
) -> Result<Vec<StoredUserExportRow>, crate::DataLayerError>;
|
||||
|
||||
async fn count_export_users(
|
||||
&self,
|
||||
query: &UserExportListQuery,
|
||||
) -> Result<u64, crate::DataLayerError>;
|
||||
|
||||
async fn summarize_export_users(&self) -> Result<UserExportSummary, crate::DataLayerError>;
|
||||
|
||||
async fn find_export_user_by_id(
|
||||
|
||||
Reference in New Issue
Block a user